Add Roster.getEntriesAndAddListener(RosterListener,RosterEntries)

This commit is contained in:
Florian Schmaus 2015-01-26 16:35:47 +01:00
parent 86ea027301
commit 7348e8bc8b
2 changed files with 96 additions and 21 deletions

View File

@ -59,6 +59,7 @@ import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jivesoftware.smack.roster.packet.RosterVer; import org.jivesoftware.smack.roster.packet.RosterVer;
import org.jivesoftware.smack.roster.packet.RosterPacket.Item; import org.jivesoftware.smack.roster.packet.RosterPacket.Item;
import org.jivesoftware.smack.roster.rosterstore.RosterStore; import org.jivesoftware.smack.roster.rosterstore.RosterStore;
import org.jivesoftware.smack.util.Objects;
import org.jxmpp.util.XmppStringUtils; import org.jxmpp.util.XmppStringUtils;
/** /**
@ -123,11 +124,22 @@ public class Roster extends Manager {
private RosterStore rosterStore; private RosterStore rosterStore;
private final Map<String, RosterGroup> groups = new ConcurrentHashMap<String, RosterGroup>(); private final Map<String, RosterGroup> groups = new ConcurrentHashMap<String, RosterGroup>();
/**
* Concurrent hash map from JID to its roster entry.
*/
private final Map<String,RosterEntry> entries = new ConcurrentHashMap<String,RosterEntry>(); private final Map<String,RosterEntry> entries = new ConcurrentHashMap<String,RosterEntry>();
private final Set<RosterEntry> unfiledEntries = new CopyOnWriteArraySet<>(); private final Set<RosterEntry> unfiledEntries = new CopyOnWriteArraySet<>();
private final Set<RosterListener> rosterListeners = new LinkedHashSet<>(); private final Set<RosterListener> rosterListeners = new LinkedHashSet<>();
private final Map<String, Map<String, Presence>> presenceMap = new ConcurrentHashMap<String, Map<String, Presence>>(); private final Map<String, Map<String, Presence>> presenceMap = new ConcurrentHashMap<String, Map<String, Presence>>();
/**
* Mutually exclude roster listener invocation and changing the {@link entries} map. Also used
* to synchronize access to either the roster listeners or the entries map.
*/
private final Object rosterListenersAndEntriesLock = new Object();
// The roster is marked as initialized when at least a single roster packet // The roster is marked as initialized when at least a single roster packet
// has been received and processed. // has been received and processed.
private boolean loaded = false; private boolean loaded = false;
@ -354,9 +366,12 @@ public class Roster extends Manager {
* *
* @param rosterListener a roster listener. * @param rosterListener a roster listener.
* @return true if the listener was not already added. * @return true if the listener was not already added.
* @see #getEntriesAndAddListener(RosterListener, RosterEntries)
*/ */
public boolean addRosterListener(RosterListener rosterListener) { public boolean addRosterListener(RosterListener rosterListener) {
return rosterListeners.add(rosterListener); synchronized (rosterListenersAndEntriesLock) {
return rosterListeners.add(rosterListener);
}
} }
/** /**
@ -367,7 +382,9 @@ public class Roster extends Manager {
* @return true if the listener was active and got removed. * @return true if the listener was active and got removed.
*/ */
public boolean removeRosterListener(RosterListener rosterListener) { public boolean removeRosterListener(RosterListener rosterListener) {
return rosterListeners.remove(rosterListener); synchronized (rosterListenersAndEntriesLock) {
return rosterListeners.remove(rosterListener);
}
} }
/** /**
@ -481,6 +498,33 @@ public class Roster extends Manager {
return getEntries().size(); return getEntries().size();
} }
/**
* Add a roster listener and invoke the roster entries with all entries of the roster.
* <p>
* The method guarantees that the listener is only invoked after
* {@link RosterEntries#rosterEntires(Collection)} has been invoked, and that all roster events
* that happen while <code>rosterEntires(Collection) </code> is called are queued until the
* method returns.
* </p>
* <p>
* This guarantee makes this the ideal method to e.g. populate a UI element with the roster while
* installing a {@link RosterListener} to listen for subsequent roster events.
* </p>
*
* @param rosterListener the listener to install
* @param rosterEntries the roster entries callback interface
* @since 4.1
*/
public void getEntriesAndAddListener(RosterListener rosterListener, RosterEntries rosterEntries) {
Objects.requireNonNull(rosterListener, "listener must not be null");
Objects.requireNonNull(rosterEntries, "rosterEntries must not be null");
synchronized (rosterListenersAndEntriesLock) {
rosterEntries.rosterEntires(entries.values());
addRosterListener(rosterListener);
}
}
/** /**
* Returns a set of all entries in the roster, including entries * Returns a set of all entries in the roster, including entries
* that don't belong to any groups. * that don't belong to any groups.
@ -488,14 +532,13 @@ public class Roster extends Manager {
* @return all entries in the roster. * @return all entries in the roster.
*/ */
public Set<RosterEntry> getEntries() { public Set<RosterEntry> getEntries() {
Set<RosterEntry> allEntries = new HashSet<RosterEntry>(); Set<RosterEntry> allEntries;
// Loop through all roster groups and add their entries to the answer synchronized (rosterListenersAndEntriesLock) {
for (RosterGroup rosterGroup : getGroups()) { allEntries = new HashSet<>(entries.size());
allEntries.addAll(rosterGroup.getEntries()); for (RosterEntry entry : entries.values()) {
allEntries.add(entry);
}
} }
// Add the roster unfiled entries to the answer
allEntries.addAll(unfiledEntries);
return allEntries; return allEntries;
} }
@ -896,15 +939,17 @@ public class Roster extends Manager {
*/ */
private void fireRosterChangedEvent(final Collection<String> addedEntries, final Collection<String> updatedEntries, private void fireRosterChangedEvent(final Collection<String> addedEntries, final Collection<String> updatedEntries,
final Collection<String> deletedEntries) { final Collection<String> deletedEntries) {
for (RosterListener listener : rosterListeners) { synchronized (rosterListenersAndEntriesLock) {
if (!addedEntries.isEmpty()) { for (RosterListener listener : rosterListeners) {
listener.entriesAdded(addedEntries); if (!addedEntries.isEmpty()) {
} listener.entriesAdded(addedEntries);
if (!updatedEntries.isEmpty()) { }
listener.entriesUpdated(updatedEntries); if (!updatedEntries.isEmpty()) {
} listener.entriesUpdated(updatedEntries);
if (!deletedEntries.isEmpty()) { }
listener.entriesDeleted(deletedEntries); if (!deletedEntries.isEmpty()) {
listener.entriesDeleted(deletedEntries);
}
} }
} }
} }
@ -915,14 +960,19 @@ public class Roster extends Manager {
* @param presence the presence change. * @param presence the presence change.
*/ */
private void fireRosterPresenceEvent(final Presence presence) { private void fireRosterPresenceEvent(final Presence presence) {
for (RosterListener listener : rosterListeners) { synchronized (rosterListenersAndEntriesLock) {
listener.presenceChanged(presence); for (RosterListener listener : rosterListeners) {
listener.presenceChanged(presence);
}
} }
} }
private void addUpdateEntry(Collection<String> addedEntries, Collection<String> updatedEntries, private void addUpdateEntry(Collection<String> addedEntries, Collection<String> updatedEntries,
Collection<String> unchangedEntries, RosterPacket.Item item, RosterEntry entry) { Collection<String> unchangedEntries, RosterPacket.Item item, RosterEntry entry) {
RosterEntry oldEntry = entries.put(item.getUser(), entry); RosterEntry oldEntry;
synchronized (rosterListenersAndEntriesLock) {
oldEntry = entries.put(item.getUser(), entry);
}
if (oldEntry == null) { if (oldEntry == null) {
addedEntries.add(item.getUser()); addedEntries.add(item.getUser());
} }

View File

@ -0,0 +1,25 @@
/**
*
* Copyright 2015 Florian Schmaus
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jivesoftware.smack.roster;
import java.util.Collection;
public interface RosterEntries {
public void rosterEntires(Collection<RosterEntry> rosterEntries);
}