Add API to make juggling Gee collections easier

This adds a simple Iterable class that lets us take advantage of Gee's
Traversable interface much more easily.  Traversable is great, but
every operation returns an Iterator, which makes it awkward to use
outside of Traversable.  The new Iterable wraps the Traversable
Iterators and methods so you can directly use the result.  It also gives
us a convenient point to add convenience methods in the future.

I've gone through a few arbitrary places in the code to see how the
class might be used, and changed some obvious places to (hopefully) the
equivalent code using the new Iterable class.  More work could be done
here, but the real benefit is simply having the Iterable class around to
be able to use in new code.
This commit is contained in:
Charles Lindsay 2013-12-12 12:42:02 -08:00
parent 2a073e8bbb
commit 62af03e511
10 changed files with 212 additions and 118 deletions

View file

@ -277,6 +277,7 @@ engine/util/util-generic-capabilities.vala
engine/util/util-html.vala
engine/util/util-imap-utf7.vala
engine/util/util-inet.vala
engine/util/util-iterable.vala
engine/util/util-numeric.vala
engine/util/util-object.vala
engine/util/util-reference-semantics.vala

View file

@ -2003,11 +2003,9 @@ public class GearyController : Geary.BaseObject {
// Returns a list of composer windows for an account, or null if none.
public Gee.List<ComposerWindow>? get_composer_windows_for_account(Geary.AccountInformation account) {
Gee.List<ComposerWindow> ret = new Gee.LinkedList<ComposerWindow>();
foreach (ComposerWindow cw in composer_windows) {
if (cw.account.information == account)
ret.add(cw);
}
Gee.LinkedList<ComposerWindow> ret = Geary.traverse<ComposerWindow>(composer_windows)
.filter(w => w.account.information == account)
.to_linked_list();
return ret.size >= 1 ? ret : null;
}

View file

@ -34,11 +34,7 @@ public class Geary.NamedFlags : BaseObject, Gee.Hashable<Geary.NamedFlags> {
}
public bool contains_any(NamedFlags flags) {
foreach(NamedFlag nf in list)
if (flags.contains(nf))
return true;
return false;
return Geary.traverse<NamedFlag>(list).any(f => flags.contains(f));
}
public Gee.Set<NamedFlag> get_all() {
@ -53,11 +49,9 @@ public class Geary.NamedFlags : BaseObject, Gee.Hashable<Geary.NamedFlags> {
}
public virtual void add_all(NamedFlags flags) {
Gee.ArrayList<NamedFlag> added = new Gee.ArrayList<NamedFlag>();
foreach (NamedFlag flag in flags.get_all()) {
if (!list.contains(flag))
added.add(flag);
}
Gee.ArrayList<NamedFlag> added = Geary.traverse<NamedFlag>(flags.get_all())
.filter(f => !list.contains(f))
.to_array_list();
list.add_all(added);
notify_added(added);
@ -72,11 +66,9 @@ public class Geary.NamedFlags : BaseObject, Gee.Hashable<Geary.NamedFlags> {
}
public virtual bool remove_all(NamedFlags flags) {
Gee.ArrayList<NamedFlag> removed = new Gee.ArrayList<NamedFlag>();
foreach (NamedFlag flag in flags.get_all()) {
if (list.contains(flag))
removed.add(flag);
}
Gee.ArrayList<NamedFlag> removed = Geary.traverse<NamedFlag>(flags.get_all())
.filter(f => list.contains(f))
.to_array_list();
list.remove_all(removed);
notify_removed(removed);
@ -91,12 +83,7 @@ public class Geary.NamedFlags : BaseObject, Gee.Hashable<Geary.NamedFlags> {
if (list.size != other.list.size)
return false;
foreach (NamedFlag flag in list) {
if (!other.contains(flag))
return false;
}
return true;
return Geary.traverse<NamedFlag>(list).all(f => other.contains(f));
}
public uint hash() {

View file

@ -88,11 +88,10 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder, Geary.FolderSupport
private void on_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable) {
if (available != null) {
foreach (Geary.Folder folder in available) {
// Exclude it from searching if it's got the right special type.
if (folder.special_folder_type in exclude_types)
exclude_folder(folder);
}
// Exclude it from searching if it's got the right special type.
foreach(Geary.Folder folder in Geary.traverse<Geary.Folder>(available)
.filter(f => f.special_folder_type in exclude_types))
exclude_folder(folder);
}
}
@ -101,14 +100,12 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder, Geary.FolderSupport
Cancellable? cancellable = null) throws Error {
low = null;
high = null;
Gee.TreeSet<ImapDB.SearchEmailIdentifier> in_folder
= new Gee.TreeSet<ImapDB.SearchEmailIdentifier>();
foreach (Geary.EmailIdentifier id in ids) {
ImapDB.SearchEmailIdentifier? search_id = id as ImapDB.SearchEmailIdentifier;
// This shouldn't require a result_mutex lock since there's no yield.
if (search_id != null && search_id in search_results)
in_folder.add(search_id);
}
// This shouldn't require a result_mutex lock since there's no yield.
Gee.TreeSet<ImapDB.SearchEmailIdentifier> in_folder = Geary.traverse<Geary.EmailIdentifier>(ids)
.cast_object<ImapDB.SearchEmailIdentifier>()
.filter(id => id in search_results)
.to_tree_set();
if (in_folder.size > 0) {
low = in_folder.first();
@ -154,13 +151,10 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder, Geary.FolderSupport
Error? error = null;
try {
Gee.ArrayList<ImapDB.SearchEmailIdentifier> relevant_ids
= new Gee.ArrayList<ImapDB.SearchEmailIdentifier>();
foreach (Geary.EmailIdentifier id in ids) {
ImapDB.SearchEmailIdentifier? search_id
= ImapDB.SearchEmailIdentifier.collection_get_email_identifier(search_results, id);
if (search_id != null)
relevant_ids.add(search_id);
}
= Geary.traverse<Geary.EmailIdentifier>(ids)
.map_nonnull<ImapDB.SearchEmailIdentifier>(
id => ImapDB.SearchEmailIdentifier.collection_get_email_identifier(search_results, id))
.to_array_list();
if (relevant_ids.size > 0)
yield do_search_async(query, null, relevant_ids, cancellable);
@ -259,22 +253,20 @@ public class Geary.SearchFolder : Geary.AbstractLocalFolder, Geary.FolderSupport
= ImapDB.SearchEmailIdentifier.array_list_from_results(yield account.local_search_async(
query, MAX_RESULT_EMAILS, 0, exclude_folders, add_ids ?? remove_ids, cancellable));
Gee.ArrayList<ImapDB.SearchEmailIdentifier> added
= new Gee.ArrayList<ImapDB.SearchEmailIdentifier>();
Gee.ArrayList<ImapDB.SearchEmailIdentifier> removed
= new Gee.ArrayList<ImapDB.SearchEmailIdentifier>();
Gee.List<ImapDB.SearchEmailIdentifier> added
= Gee.List.empty<ImapDB.SearchEmailIdentifier>();
Gee.List<ImapDB.SearchEmailIdentifier> removed
= Gee.List.empty<ImapDB.SearchEmailIdentifier>();
if (remove_ids == null) {
foreach (ImapDB.SearchEmailIdentifier id in results) {
if (!(id in search_results))
added.add(id);
}
added = Geary.traverse<ImapDB.SearchEmailIdentifier>(results)
.filter(id => !(id in search_results))
.to_array_list();
}
if (add_ids == null) {
foreach (ImapDB.SearchEmailIdentifier id in remove_ids ?? search_results) {
if (!(id in results))
removed.add(id);
}
removed = Geary.traverse<ImapDB.SearchEmailIdentifier>(remove_ids ?? search_results)
.filter(id => !(id in results))
.to_array_list();
}
search_results.remove_all(removed);

View file

@ -417,14 +417,9 @@ public class Geary.App.ConversationMonitor : BaseObject {
Gee.HashSet<Geary.EmailIdentifier> relevant_ids = new Gee.HashSet<Geary.EmailIdentifier>();
foreach (Geary.Email email in emails) {
Gee.Set<RFC822.MessageID>? ancestors = email.get_ancestors();
if (ancestors != null) {
foreach (RFC822.MessageID ancestor in ancestors) {
if (conversations.has_message_id(ancestor)) {
relevant_ids.add(email.id);
break;
}
}
}
if (ancestors != null &&
Geary.traverse<RFC822.MessageID>(ancestors).any(id => conversations.has_message_id(id)))
relevant_ids.add(email.id);
}
debug("%d external emails are relevant to current conversations", relevant_ids.size);
@ -485,10 +480,9 @@ public class Geary.App.ConversationMonitor : BaseObject {
Gee.Set<RFC822.MessageID>? ancestors = email.get_ancestors();
if (ancestors != null) {
foreach (RFC822.MessageID ancestor in ancestors) {
if (!new_message_ids.contains(ancestor))
new_message_ids.add(ancestor);
}
Geary.traverse<RFC822.MessageID>(ancestors)
.filter(id => !new_message_ids.contains(id))
.add_all_to(new_message_ids);
}
}
}
@ -573,10 +567,9 @@ public class Geary.App.ConversationMonitor : BaseObject {
foreach (int id in batch.get_ids()) {
LocalSearchOperation op = (LocalSearchOperation) batch.get_operation(id);
if (op.emails != null) {
foreach (Geary.Email email in op.emails.get_keys()) {
if (!needed_messages.has_key(email.id))
needed_messages.set(email.id, email);
}
Geary.traverse<Geary.Email>(op.emails.get_keys())
.filter(e => !needed_messages.has_key(e.id))
.add_all_to_map<Geary.EmailIdentifier>(needed_messages, e => e.id);
}
}

View file

@ -64,18 +64,14 @@ private class Geary.App.ConversationSet : BaseObject {
// the ancestors of the supplied Email ... if more than one, then add_email() should not be
// called
private Gee.Set<Conversation> get_associated_conversations(Geary.Email email) {
Gee.Set<Conversation> associated = new Gee.HashSet<Conversation>();
Gee.Set<Geary.RFC822.MessageID>? ancestors = email.get_ancestors();
if (ancestors != null) {
foreach (Geary.RFC822.MessageID ancestor in ancestors) {
Conversation conversation = logical_message_id_map.get(ancestor);
if (conversation != null)
associated.add(conversation);
}
return Geary.traverse<Geary.RFC822.MessageID>(ancestors)
.map_nonnull<Conversation>(a => logical_message_id_map.get(a))
.to_hash_set();
}
return associated;
return Gee.Set.empty<Conversation>();
}
/**

View file

@ -2070,19 +2070,15 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
private int do_get_unread_count_for_ids(Db.Connection cx,
Gee.Collection<ImapDB.EmailIdentifier> ids, Cancellable? cancellable) throws Error {
int unread_count = 0;
// Fetch flags for each email and update this folder's unread count.
// (Note that this only flags for emails which have NOT been marked for removal
// are included.)
Gee.Map<ImapDB.EmailIdentifier, Geary.EmailFlags>? flag_map = do_get_email_flags(cx,
ids, cancellable);
if (flag_map != null) {
foreach (Geary.EmailFlags flags in flag_map.values)
unread_count += flags.is_unread() ? 1 : 0;
}
if (flag_map != null)
return Geary.traverse<Geary.EmailFlags>(flag_map.values).count_matching(f => f.is_unread());
return unread_count;
return 0;
}
}

View file

@ -241,16 +241,14 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
throws Error {
check_open();
Gee.ArrayList<Geary.Folder> matches = new Gee.ArrayList<Geary.Folder>();
foreach(FolderPath path in folder_map.keys) {
FolderPath? path_parent = path.get_parent();
if ((parent == null && path_parent == null) ||
(parent != null && path_parent != null && path_parent.equal_to(parent))) {
matches.add(folder_map.get(path));
}
}
return matches;
return Geary.traverse<FolderPath>(folder_map.keys)
.filter(p => {
FolderPath? path_parent = p.get_parent();
return ((parent == null && path_parent == null) ||
(parent != null && path_parent != null && path_parent.equal_to(parent)));
})
.map<Geary.Folder>(p => folder_map.get(p))
.to_array_list();
}
public override Gee.Collection<Geary.Folder> list_folders() throws Error {
@ -316,9 +314,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
existing_list.add_all(build_folders(local_children.values));
existing_list.add_all(local_only.values);
Gee.HashMap<FolderPath, Geary.Folder> existing_folders = new Gee.HashMap<FolderPath, Geary.Folder>();
foreach (Geary.Folder folder in existing_list)
existing_folders.set(folder.path, folder);
Gee.HashMap<FolderPath, Geary.Folder> existing_folders
= Geary.traverse<Geary.Folder>(existing_list).to_hash_map<FolderPath>(f => f.path);
// get all remote (server) folder paths
Gee.HashMap<FolderPath, Imap.Folder> remote_folders = yield enumerate_remote_folders_async(null,
@ -467,18 +464,16 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
}
// If path in remote but not local, need to add it
Gee.List<Imap.Folder>? to_add = new Gee.ArrayList<Imap.Folder>();
foreach (Imap.Folder remote_folder in remote_folders.values) {
if (!existing_folders.has_key(remote_folder.path))
to_add.add(remote_folder);
}
Gee.ArrayList<Imap.Folder> to_add = Geary.traverse<Imap.Folder>(remote_folders.values)
.filter(f => !existing_folders.has_key(f.path))
.to_array_list();
// If path in local but not remote (and isn't local-only, i.e. the Outbox), need to remove it
Gee.List<Geary.Folder>? to_remove = new Gee.ArrayList<Geary.Folder>();
foreach (Geary.FolderPath existing_path in existing_folders.keys) {
if (!remote_folders.has_key(existing_path) && !local_only.has_key(existing_path))
to_remove.add(existing_folders.get(existing_path));
}
Gee.ArrayList<Geary.Folder> to_remove
= Geary.traverse<Gee.Map.Entry<FolderPath, Imap.Folder>>(existing_folders)
.filter(e => !remote_folders.has_key(e.key) && !local_only.has_key(e.key))
.map<Geary.Folder>(e => (Geary.Folder) e.value)
.to_array_list();
// For folders to add, clone them and their properties locally
foreach (Geary.Imap.Folder remote_folder in to_add) {

View file

@ -55,12 +55,7 @@ public abstract class Geary.Imap.Flags : Geary.MessageData.AbstractMessageData,
if (other.size != size)
return false;
foreach (Flag flag in list) {
if (!other.contains(flag))
return false;
}
return true;
return Geary.traverse<Flag>(list).all(f => other.contains(f));
}
public override string to_string() {

View file

@ -0,0 +1,141 @@
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
namespace Geary {
public Geary.Iterable<G> traverse<G>(Gee.Iterable<G> i) {
return new Geary.Iterable<G>(i.iterator());
}
}
/**
* An Iterable that simply wraps an existing Iterator. You get one iteration,
* and only one iteration. Basically every method triggers one iteration and
* returns a new object.
*
* Note that this can't inherit from Gee.Iterable because its interface
* requires that map/filter/etc. return Iterators, not Iterables. It still
* works in foreach.
*/
public class Geary.Iterable<G> : BaseObject {
private Gee.Iterator<G> i;
public Iterable(Gee.Iterator<G> iterator) {
i = iterator;
}
public virtual Gee.Iterator<G> iterator() {
return i;
}
public Iterable<A> map<A>(Gee.MapFunc<A, G> f) {
return new Iterable<A>(i.map<A>(f));
}
public Iterable<A> scan<A>(Gee.FoldFunc<A, G> f, owned A seed) {
return new Iterable<A>(i.scan<A>(f, seed));
}
public Iterable<G> filter(owned Gee.Predicate<G> f) {
return new Iterable<G>(i.filter(f));
}
public Iterable<G> chop(int offset, int length = -1) {
return new Iterable<G>(i.chop(offset, length));
}
public Iterable<A> map_nonnull<A>(Gee.MapFunc<A, G> f) {
return new Iterable<A>(i.map<A>(f).filter(g => g != null));
}
/**
* Return only objects of the destination type, as the destination type.
* Only works on types derived from Object.
*/
public Iterable<A> cast_object<A>() {
return new Iterable<G>(
// This would be a lot simpler if valac didn't barf on the shorter,
// more obvious syntax for each of these delegates here.
i.filter(g => ((Object) g).get_type().is_a(typeof(A)))
.map<A>(g => { return (A) g; }));
}
public G? first() {
return (i.next() ? i.@get() : null);
}
public G? first_matching(owned Gee.Predicate<G> f) {
foreach (G g in this) {
if (f(g))
return g;
}
return null;
}
public bool any(owned Gee.Predicate<G> f) {
foreach (G g in this) {
if (f(g))
return true;
}
return false;
}
public bool all(owned Gee.Predicate<G> f) {
foreach (G g in this) {
if (!f(g))
return false;
}
return true;
}
public int count_matching(owned Gee.Predicate<G> f) {
int count = 0;
foreach (G g in this) {
if (f(g))
count++;
}
return count;
}
public Gee.Collection<G> add_all_to(Gee.Collection<G> c) {
while (i.next())
c.add(i.@get());
return c;
}
public Gee.ArrayList<G> to_array_list(owned Gee.EqualDataFunc<G>? equal_func = null) {
return (Gee.ArrayList<G>) add_all_to(new Gee.ArrayList<G>(equal_func));
}
public Gee.LinkedList<G> to_linked_list(owned Gee.EqualDataFunc<G>? equal_func = null) {
return (Gee.LinkedList<G>) add_all_to(new Gee.LinkedList<G>(equal_func));
}
public Gee.HashSet<G> to_hash_set(owned Gee.HashDataFunc<G>? hash_func = null,
owned Gee.EqualDataFunc<G>? equal_func = null) {
return (Gee.HashSet<G>) add_all_to(new Gee.HashSet<G>(hash_func, equal_func));
}
public Gee.TreeSet<G> to_tree_set(owned CompareDataFunc<G>? compare_func = null) {
return (Gee.TreeSet<G>) add_all_to(new Gee.TreeSet<G>(compare_func));
}
public Gee.Map<K, G> add_all_to_map<K>(Gee.Map<K, G> c, Gee.MapFunc<K, G> key_func) {
while (i.next()) {
G g = i.@get();
c.@set(key_func(g), g);
}
return c;
}
public Gee.HashMap<K, G> to_hash_map<K>(Gee.MapFunc<K, G> key_func,
owned Gee.HashDataFunc<K>? key_hash_func = null,
owned Gee.EqualDataFunc<K>? key_equal_func = null,
owned Gee.EqualDataFunc<G>? value_equal_func = null) {
return (Gee.HashMap<K, G>) add_all_to_map<K>(new Gee.HashMap<K, G>(
key_hash_func, key_equal_func, value_equal_func), key_func);
}
}