mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Add a generic multiset implementation.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package org.briarproject.bramble.api;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
public class Multiset<T> {
|
||||
|
||||
private final Map<T, Integer> map = new HashMap<>();
|
||||
|
||||
private int total = 0;
|
||||
|
||||
/**
|
||||
* Returns how many items the multiset contains in total.
|
||||
*/
|
||||
public int getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many unique items the multiset contains.
|
||||
*/
|
||||
public int getUnique() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many of the given item the multiset contains.
|
||||
*/
|
||||
public int getCount(T t) {
|
||||
Integer count = map.get(t);
|
||||
return count == null ? 0 : count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given item to the multiset and returns how many of the item
|
||||
* the multiset now contains.
|
||||
*/
|
||||
public int add(T t) {
|
||||
Integer count = map.get(t);
|
||||
if (count == null) count = 0;
|
||||
map.put(t, count + 1);
|
||||
total++;
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given item from the multiset and returns how many of the
|
||||
* item the multiset now contains.
|
||||
* @throws NoSuchElementException if the item is not in the multiset.
|
||||
*/
|
||||
public int remove(T t) {
|
||||
Integer count = map.get(t);
|
||||
if (count == null) throw new NoSuchElementException();
|
||||
if (count == 1) map.remove(t);
|
||||
else map.put(t, count - 1);
|
||||
total--;
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all occurrences of the given item from the multiset.
|
||||
*/
|
||||
public int removeAll(T t) {
|
||||
Integer count = map.remove(t);
|
||||
if (count == null) return 0;
|
||||
total -= count;
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the multiset contains any occurrences of the given item.
|
||||
*/
|
||||
public boolean contains(T t) {
|
||||
return map.containsKey(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the multiset.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
total = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of unique items the multiset contains. The returned set
|
||||
* is unmodifiable.
|
||||
*/
|
||||
public Set<T> keySet() {
|
||||
return Collections.unmodifiableSet(map.keySet());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.briarproject.bramble.plugin;
|
||||
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -36,14 +37,14 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
// The following are locking: lock
|
||||
private final Map<TransportId, Map<ContactId, Integer>> connections;
|
||||
private final Map<ContactId, Integer> contactCounts;
|
||||
private final Map<TransportId, Multiset<ContactId>> connections;
|
||||
private final Multiset<ContactId> contactCounts;
|
||||
|
||||
@Inject
|
||||
ConnectionRegistryImpl(EventBus eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
connections = new HashMap<>();
|
||||
contactCounts = new HashMap<>();
|
||||
contactCounts = new Multiset<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,21 +57,13 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
boolean firstConnection = false;
|
||||
lock.lock();
|
||||
try {
|
||||
Map<ContactId, Integer> m = connections.get(t);
|
||||
Multiset<ContactId> m = connections.get(t);
|
||||
if (m == null) {
|
||||
m = new HashMap<>();
|
||||
m = new Multiset<>();
|
||||
connections.put(t, m);
|
||||
}
|
||||
Integer count = m.get(c);
|
||||
if (count == null) m.put(c, 1);
|
||||
else m.put(c, count + 1);
|
||||
count = contactCounts.get(c);
|
||||
if (count == null) {
|
||||
firstConnection = true;
|
||||
contactCounts.put(c, 1);
|
||||
} else {
|
||||
contactCounts.put(c, count + 1);
|
||||
}
|
||||
m.add(c);
|
||||
if (contactCounts.add(c) == 1) firstConnection = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -91,23 +84,10 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
boolean lastConnection = false;
|
||||
lock.lock();
|
||||
try {
|
||||
Map<ContactId, Integer> m = connections.get(t);
|
||||
Multiset<ContactId> m = connections.get(t);
|
||||
if (m == null) throw new IllegalArgumentException();
|
||||
Integer count = m.remove(c);
|
||||
if (count == null) throw new IllegalArgumentException();
|
||||
if (count == 1) {
|
||||
if (m.isEmpty()) connections.remove(t);
|
||||
} else {
|
||||
m.put(c, count - 1);
|
||||
}
|
||||
count = contactCounts.get(c);
|
||||
if (count == null) throw new IllegalArgumentException();
|
||||
if (count == 1) {
|
||||
lastConnection = true;
|
||||
contactCounts.remove(c);
|
||||
} else {
|
||||
contactCounts.put(c, count - 1);
|
||||
}
|
||||
m.remove(c);
|
||||
if (contactCounts.remove(c) == 0) lastConnection = true;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -122,7 +102,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
public Collection<ContactId> getConnectedContacts(TransportId t) {
|
||||
lock.lock();
|
||||
try {
|
||||
Map<ContactId, Integer> m = connections.get(t);
|
||||
Multiset<ContactId> m = connections.get(t);
|
||||
if (m == null) return Collections.emptyList();
|
||||
List<ContactId> ids = new ArrayList<>(m.keySet());
|
||||
if (LOG.isLoggable(INFO))
|
||||
@@ -137,8 +117,8 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
public boolean isConnected(ContactId c, TransportId t) {
|
||||
lock.lock();
|
||||
try {
|
||||
Map<ContactId, Integer> m = connections.get(t);
|
||||
return m != null && m.containsKey(c);
|
||||
Multiset<ContactId> m = connections.get(t);
|
||||
return m != null && m.contains(c);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
@@ -148,7 +128,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
|
||||
public boolean isConnected(ContactId c) {
|
||||
lock.lock();
|
||||
try {
|
||||
return contactCounts.containsKey(c);
|
||||
return contactCounts.contains(c);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
|
||||
import org.briarproject.bramble.api.Multiset;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DatabaseExecutor;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
@@ -45,8 +46,7 @@ import org.briarproject.briar.api.privategroup.event.GroupMessageAddedEvent;
|
||||
import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -109,11 +109,10 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
|
||||
// The following must only be accessed on the main UI thread
|
||||
private final Map<ContactId, Integer> contactCounts = new HashMap<>();
|
||||
private final Map<GroupId, Integer> groupCounts = new HashMap<>();
|
||||
private final Map<GroupId, Integer> forumCounts = new HashMap<>();
|
||||
private final Map<GroupId, Integer> blogCounts = new HashMap<>();
|
||||
private int contactTotal = 0, groupTotal = 0, forumTotal = 0, blogTotal = 0;
|
||||
private final Multiset<ContactId> contactCounts = new Multiset<>();
|
||||
private final Multiset<GroupId> groupCounts = new Multiset<>();
|
||||
private final Multiset<GroupId> forumCounts = new Multiset<>();
|
||||
private final Multiset<GroupId> blogCounts = new Multiset<>();
|
||||
private int introductionTotal = 0;
|
||||
private int nextRequestId = 0;
|
||||
private ContactId blockedContact = null;
|
||||
@@ -197,28 +196,24 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@UiThread
|
||||
private void clearContactNotification() {
|
||||
contactCounts.clear();
|
||||
contactTotal = 0;
|
||||
notificationManager.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearGroupMessageNotification() {
|
||||
groupCounts.clear();
|
||||
groupTotal = 0;
|
||||
notificationManager.cancel(GROUP_MESSAGE_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearForumPostNotification() {
|
||||
forumCounts.clear();
|
||||
forumTotal = 0;
|
||||
notificationManager.cancel(FORUM_POST_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void clearBlogPostNotification() {
|
||||
blogCounts.clear();
|
||||
blogTotal = 0;
|
||||
notificationManager.cancel(BLOG_POST_NOTIFICATION_ID);
|
||||
}
|
||||
|
||||
@@ -278,10 +273,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
if (blockContacts) return;
|
||||
if (c.equals(blockedContact)) return;
|
||||
Integer count = contactCounts.get(c);
|
||||
if (count == null) contactCounts.put(c, 1);
|
||||
else contactCounts.put(c, count + 1);
|
||||
contactTotal++;
|
||||
contactCounts.add(c);
|
||||
updateContactNotification(true);
|
||||
});
|
||||
}
|
||||
@@ -289,15 +281,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@Override
|
||||
public void clearContactNotification(ContactId c) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
Integer count = contactCounts.remove(c);
|
||||
if (count == null) return; // Already cleared
|
||||
contactTotal -= count;
|
||||
updateContactNotification(false);
|
||||
if (contactCounts.removeAll(c) > 0)
|
||||
updateContactNotification(false);
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateContactNotification(boolean mayAlertAgain) {
|
||||
int contactTotal = contactCounts.getTotal();
|
||||
if (contactTotal == 0) {
|
||||
clearContactNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
|
||||
@@ -315,10 +306,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
b.setLockscreenVisibility(CATEGORY_MESSAGE, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, CONTACT_URI);
|
||||
if (contactCounts.size() == 1) {
|
||||
Set<ContactId> contacts = contactCounts.keySet();
|
||||
if (contacts.size() == 1) {
|
||||
// Touching the notification shows the relevant conversation
|
||||
Intent i = new Intent(appContext, ConversationActivity.class);
|
||||
ContactId c = contactCounts.keySet().iterator().next();
|
||||
ContactId c = contacts.iterator().next();
|
||||
i.putExtra(CONTACT_ID, c.getInt());
|
||||
i.setData(Uri.parse(CONTACT_URI + "/" + c.getInt()));
|
||||
i.setFlags(FLAG_ACTIVITY_CLEAR_TOP);
|
||||
@@ -385,10 +377,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
if (blockGroups) return;
|
||||
if (g.equals(blockedGroup)) return;
|
||||
Integer count = groupCounts.get(g);
|
||||
if (count == null) groupCounts.put(g, 1);
|
||||
else groupCounts.put(g, count + 1);
|
||||
groupTotal++;
|
||||
groupCounts.add(g);
|
||||
updateGroupMessageNotification(true);
|
||||
});
|
||||
}
|
||||
@@ -396,15 +385,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@Override
|
||||
public void clearGroupMessageNotification(GroupId g) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
Integer count = groupCounts.remove(g);
|
||||
if (count == null) return; // Already cleared
|
||||
groupTotal -= count;
|
||||
updateGroupMessageNotification(false);
|
||||
if (groupCounts.removeAll(g) > 0)
|
||||
updateGroupMessageNotification(false);
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateGroupMessageNotification(boolean mayAlertAgain) {
|
||||
int groupTotal = groupCounts.getTotal();
|
||||
if (groupTotal == 0) {
|
||||
clearGroupMessageNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) {
|
||||
@@ -422,10 +410,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, GROUP_URI);
|
||||
if (groupCounts.size() == 1) {
|
||||
Set<GroupId> groups = groupCounts.keySet();
|
||||
if (groups.size() == 1) {
|
||||
// Touching the notification shows the relevant group
|
||||
Intent i = new Intent(appContext, GroupActivity.class);
|
||||
GroupId g = groupCounts.keySet().iterator().next();
|
||||
GroupId g = groups.iterator().next();
|
||||
i.putExtra(GROUP_ID, g.getBytes());
|
||||
String idHex = StringUtils.toHexString(g.getBytes());
|
||||
i.setData(Uri.parse(GROUP_URI + "/" + idHex));
|
||||
@@ -461,10 +450,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
if (blockForums) return;
|
||||
if (g.equals(blockedGroup)) return;
|
||||
Integer count = forumCounts.get(g);
|
||||
if (count == null) forumCounts.put(g, 1);
|
||||
else forumCounts.put(g, count + 1);
|
||||
forumTotal++;
|
||||
forumCounts.add(g);
|
||||
updateForumPostNotification(true);
|
||||
});
|
||||
}
|
||||
@@ -472,15 +458,14 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@Override
|
||||
public void clearForumPostNotification(GroupId g) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
Integer count = forumCounts.remove(g);
|
||||
if (count == null) return; // Already cleared
|
||||
forumTotal -= count;
|
||||
updateForumPostNotification(false);
|
||||
if (forumCounts.removeAll(g) > 0)
|
||||
updateForumPostNotification(false);
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateForumPostNotification(boolean mayAlertAgain) {
|
||||
int forumTotal = forumCounts.getTotal();
|
||||
if (forumTotal == 0) {
|
||||
clearForumPostNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
|
||||
@@ -498,10 +483,11 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
|
||||
if (mayAlertAgain) setAlertProperties(b);
|
||||
setDeleteIntent(b, FORUM_URI);
|
||||
if (forumCounts.size() == 1) {
|
||||
Set<GroupId> forums = forumCounts.keySet();
|
||||
if (forums.size() == 1) {
|
||||
// Touching the notification shows the relevant forum
|
||||
Intent i = new Intent(appContext, ForumActivity.class);
|
||||
GroupId g = forumCounts.keySet().iterator().next();
|
||||
GroupId g = forums.iterator().next();
|
||||
i.putExtra(GROUP_ID, g.getBytes());
|
||||
String idHex = StringUtils.toHexString(g.getBytes());
|
||||
i.setData(Uri.parse(FORUM_URI + "/" + idHex));
|
||||
@@ -536,10 +522,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
if (blockBlogs) return;
|
||||
if (g.equals(blockedGroup)) return;
|
||||
Integer count = blogCounts.get(g);
|
||||
if (count == null) blogCounts.put(g, 1);
|
||||
else blogCounts.put(g, count + 1);
|
||||
blogTotal++;
|
||||
blogCounts.add(g);
|
||||
updateBlogPostNotification(true);
|
||||
});
|
||||
}
|
||||
@@ -547,15 +530,13 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
|
||||
@Override
|
||||
public void clearBlogPostNotification(GroupId g) {
|
||||
androidExecutor.runOnUiThread(() -> {
|
||||
Integer count = blogCounts.remove(g);
|
||||
if (count == null) return; // Already cleared
|
||||
blogTotal -= count;
|
||||
updateBlogPostNotification(false);
|
||||
if (blogCounts.removeAll(g) > 0) updateBlogPostNotification(false);
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
private void updateBlogPostNotification(boolean mayAlertAgain) {
|
||||
int blogTotal = blogCounts.getTotal();
|
||||
if (blogTotal == 0) {
|
||||
clearBlogPostNotification();
|
||||
} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
|
||||
|
||||
Reference in New Issue
Block a user