/** * * Copyright 2010 Jive Software. * * 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 static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.jivesoftware.smack.DummyConnection; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.im.InitSmackIm; import org.jivesoftware.smack.packet.ErrorIQ; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.IQ.Type; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.XMPPError.Condition; import org.jivesoftware.smack.roster.packet.RosterPacket; import org.jivesoftware.smack.roster.packet.RosterPacket.Item; import org.jivesoftware.smack.roster.packet.RosterPacket.ItemType; import org.jivesoftware.smack.test.util.TestUtils; import org.jivesoftware.smack.test.util.WaitForPacketListener; import org.jivesoftware.smack.util.PacketParserUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.jxmpp.jid.BareJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.stringprep.XmppStringprepException; import org.xmlpull.v1.XmlPullParser; /** * Tests that verifies the correct behavior of the {@link Roster} implementation. * * @see Roster * @see Roster Management * @author Guenther Niess */ public class RosterTest extends InitSmackIm { private DummyConnection connection; private Roster roster; private TestRosterListener rosterListener; @Before public void setUp() throws Exception { connection = new DummyConnection(); connection.connect(); connection.login(); rosterListener = new TestRosterListener(); roster = Roster.getInstanceFor(connection); roster.addRosterListener(rosterListener); connection.setReplyTimeout(1000 * 60 * 5); } @After public void tearDown() throws Exception { if (connection != null) { if (rosterListener != null) { roster.removeRosterListener(rosterListener); rosterListener = null; } connection.disconnect(); connection = null; } } /** * Test a simple roster initialization according to the example in * RFC3921: Retrieving One's Roster on Login. */ @Test public void testSimpleRosterInitialization() throws Exception { assertNotNull("Can't get the roster from the provided connection!", roster); assertFalse("Roster shouldn't be already loaded!", roster.isLoaded()); // Perform roster initialization initRoster(); // Verify roster assertTrue("Roster can't be loaded!", roster.waitUntilLoaded()); verifyRomeosEntry(roster.getEntry(JidCreate.entityBareFrom("romeo@example.net"))); verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries.", 3, roster.getEntries().size()); // Verify roster listener assertTrue("The roster listener wasn't invoked for Romeo.", rosterListener.addedAddressesContains("romeo@example.net")); assertTrue("The roster listener wasn't invoked for Mercutio.", rosterListener.addedAddressesContains("mercutio@example.com")); assertTrue("The roster listener wasn't invoked for Benvolio.", rosterListener.addedAddressesContains("benvolio@example.net")); assertSame("RosterListeners implies that a item was deleted!", 0, rosterListener.getDeletedAddresses().size()); assertSame("RosterListeners implies that a item was updated!", 0, rosterListener.getUpdatedAddresses().size()); } /** * Test adding a roster item according to the example in * RFC3921: Adding a Roster Item. */ @Test public void testAddRosterItem() throws Throwable { // Constants for the new contact final BareJid contactJID = JidCreate.entityBareFrom("nurse@example.com"); final String contactName = "Nurse"; final String[] contactGroup = {"Servants"}; // Setup assertNotNull("Can't get the roster from the provided connection!", roster); initRoster(); rosterListener.reset(); // Adding the new roster item final RosterUpdateResponder serverSimulator = new RosterUpdateResponder() { @Override void verifyUpdateRequest(final RosterPacket updateRequest) { final Item item = updateRequest.getRosterItems().iterator().next(); assertEquals("The provided JID doesn't match the requested!", contactJID, item.getJid()); assertSame("The provided name doesn't match the requested!", contactName, item.getName()); assertSame("The provided group number doesn't match the requested!", contactGroup.length, item.getGroupNames().size()); assertSame("The provided group doesn't match the requested!", contactGroup[0], item.getGroupNames().iterator().next()); } }; serverSimulator.start(); roster.createEntry(contactJID, contactName, contactGroup); serverSimulator.join(); // Check if an error occurred within the simulator final Throwable exception = serverSimulator.getException(); if (exception != null) { throw exception; } rosterListener.waitUntilInvocationOrTimeout(); // Verify the roster entry of the new contact final RosterEntry addedEntry = roster.getEntry(contactJID); assertNotNull("The new contact wasn't added to the roster!", addedEntry); assertTrue("The roster listener wasn't invoked for the new contact!", rosterListener.getAddedAddresses().contains(contactJID)); assertSame("Setup wrong name for the new contact!", contactName, addedEntry.getName()); assertSame("Setup wrong default subscription status!", ItemType.none, addedEntry.getType()); assertSame("The new contact should be member of exactly one group!", 1, addedEntry.getGroups().size()); assertSame("Setup wrong group name for the added contact!", contactGroup[0], addedEntry.getGroups().iterator().next().getName()); // Verify the unchanged roster items verifyRomeosEntry(roster.getEntry(JidCreate.entityBareFrom("romeo@example.net"))); verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries.", 4, roster.getEntries().size()); } /** * Test updating a roster item according to the example in * RFC3921: Updating a Roster Item. */ @Test public void testUpdateRosterItem() throws Throwable { // Constants for the updated contact final BareJid contactJID = JidCreate.entityBareFrom("romeo@example.net"); final String contactName = "Romeo"; final String[] contactGroups = {"Friends", "Lovers"}; // Setup assertNotNull("Can't get the roster from the provided connection!", roster); initRoster(); rosterListener.reset(); // Updating the roster item final RosterUpdateResponder serverSimulator = new RosterUpdateResponder() { @Override void verifyUpdateRequest(final RosterPacket updateRequest) { final Item item = updateRequest.getRosterItems().iterator().next(); assertEquals("The provided JID doesn't match the requested!", contactJID, item.getJid()); assertSame("The provided name doesn't match the requested!", contactName, item.getName()); assertTrue("The updated contact doesn't belong to the requested groups (" + contactGroups[0] + ")!", item.getGroupNames().contains(contactGroups[0])); assertTrue("The updated contact doesn't belong to the requested groups (" + contactGroups[1] + ")!", item.getGroupNames().contains(contactGroups[1])); assertSame("The provided group number doesn't match the requested!", contactGroups.length, item.getGroupNames().size()); } }; serverSimulator.start(); roster.createGroup(contactGroups[1]).addEntry(roster.getEntry(contactJID)); serverSimulator.join(); // Check if an error occurred within the simulator final Throwable exception = serverSimulator.getException(); if (exception != null) { throw exception; } rosterListener.waitUntilInvocationOrTimeout(); // Verify the roster entry of the updated contact final RosterEntry addedEntry = roster.getEntry(contactJID); assertNotNull("The contact was deleted from the roster!", addedEntry); assertTrue("The roster listener wasn't invoked for the updated contact!", rosterListener.getUpdatedAddresses().contains(contactJID)); assertSame("Setup wrong name for the changed contact!", contactName, addedEntry.getName()); assertTrue("The updated contact doesn't belong to the requested groups (" + contactGroups[0] + ")!", roster.getGroup(contactGroups[0]).contains(addedEntry)); assertTrue("The updated contact doesn't belong to the requested groups (" + contactGroups[1] + ")!", roster.getGroup(contactGroups[1]).contains(addedEntry)); assertSame("The updated contact should be member of two groups!", contactGroups.length, addedEntry.getGroups().size()); // Verify the unchanged roster items verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries (" + roster.getEntries() + ").", 3, roster.getEntries().size()); } /** * Test deleting a roster item according to the example in * RFC3921: Deleting a Roster Item. */ @Test public void testDeleteRosterItem() throws Throwable { // The contact which should be deleted final BareJid contactJID = JidCreate.entityBareFrom("romeo@example.net"); // Setup assertNotNull("Can't get the roster from the provided connection!", roster); initRoster(); rosterListener.reset(); // Delete a roster item final RosterUpdateResponder serverSimulator = new RosterUpdateResponder() { @Override void verifyUpdateRequest(final RosterPacket updateRequest) { final Item item = updateRequest.getRosterItems().iterator().next(); assertEquals("The provided JID doesn't match the requested!", contactJID, item.getJid()); } }; serverSimulator.start(); roster.removeEntry(roster.getEntry(contactJID)); serverSimulator.join(); // Check if an error occurred within the simulator final Throwable exception = serverSimulator.getException(); if (exception != null) { throw exception; } rosterListener.waitUntilInvocationOrTimeout(); // Verify final RosterEntry deletedEntry = roster.getEntry(contactJID); assertNull("The contact wasn't deleted from the roster!", deletedEntry); assertTrue("The roster listener wasn't invoked for the deleted contact!", rosterListener.getDeletedAddresses().contains(contactJID)); verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries (" + roster.getEntries() + ").", 2, roster.getEntries().size()); } /** * Test a simple roster push according to the example in * RFC3921bis-03: Roster Push. */ @Test public void testSimpleRosterPush() throws Throwable { final BareJid contactJID = JidCreate.entityBareFrom("nurse@example.com"); assertNotNull("Can't get the roster from the provided connection!", roster); final StringBuilder sb = new StringBuilder(); sb.append("") .append("") .append("") .append("") .append(""); final XmlPullParser parser = TestUtils.getIQParser(sb.toString()); final IQ rosterPush = PacketParserUtils.parseIQ(parser); initRoster(); rosterListener.reset(); // Simulate receiving the roster push connection.processStanza(rosterPush); rosterListener.waitUntilInvocationOrTimeout(); // Verify the roster entry of the new contact final RosterEntry addedEntry = roster.getEntry(contactJID); assertNotNull("The new contact wasn't added to the roster!", addedEntry); assertTrue("The roster listener wasn't invoked for the new contact!", rosterListener.getAddedAddresses().contains(contactJID)); assertSame("Setup wrong default subscription status!", ItemType.none, addedEntry.getType()); assertSame("The new contact shouldn't be member of any group!", 0, addedEntry.getGroups().size()); // Verify the unchanged roster items verifyRomeosEntry(roster.getEntry(JidCreate.entityBareFrom("romeo@example.net"))); verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries.", 4, roster.getEntries().size()); } /** * Tests that roster pushes with invalid from are ignored. * @throws XmppStringprepException * * @see RFC 6121, Section 2.1.6 */ @Test public void testIgnoreInvalidFrom() throws XmppStringprepException { final BareJid spammerJid = JidCreate.entityBareFrom("spam@example.com"); RosterPacket packet = new RosterPacket(); packet.setType(Type.set); packet.setTo(connection.getUser()); packet.setFrom(JidCreate.entityBareFrom("mallory@example.com")); packet.addRosterItem(new Item(spammerJid, "Cool products!")); final String requestId = packet.getStanzaId(); // Simulate receiving the roster push connection.processStanza(packet); // Smack should reply with an error IQ ErrorIQ errorIQ = connection.getSentPacket(); assertEquals(requestId, errorIQ.getStanzaId()); assertEquals(Condition.service_unavailable, errorIQ.getError().getCondition()); assertNull("Contact was added to roster", Roster.getInstanceFor(connection).getEntry(spammerJid)); } /** * Test if adding an user with an empty group is equivalent with providing * no group. * * @see SMACK-294 */ @Test(timeout = 5000) public void testAddEmptyGroupEntry() throws Throwable { // Constants for the new contact final BareJid contactJID = JidCreate.entityBareFrom("nurse@example.com"); final String contactName = "Nurse"; final String[] contactGroup = {""}; // Setup assertNotNull("Can't get the roster from the provided connection!", roster); initRoster(); rosterListener.reset(); // Adding the new roster item final RosterUpdateResponder serverSimulator = new RosterUpdateResponder() { @Override void verifyUpdateRequest(final RosterPacket updateRequest) { final Item item = updateRequest.getRosterItems().iterator().next(); assertSame("The provided JID doesn't match the requested!", contactJID, item.getJid()); assertSame("The provided name doesn't match the requested!", contactName, item.getName()); assertSame("Shouldn't provide an empty group element!", 0, item.getGroupNames().size()); } }; serverSimulator.start(); roster.createEntry(contactJID, contactName, contactGroup); serverSimulator.join(); // Check if an error occurred within the simulator final Throwable exception = serverSimulator.getException(); if (exception != null) { throw exception; } rosterListener.waitUntilInvocationOrTimeout(); // Verify the roster entry of the new contact final RosterEntry addedEntry = roster.getEntry(contactJID); assertNotNull("The new contact wasn't added to the roster!", addedEntry); assertTrue("The roster listener wasn't invoked for the new contact!", rosterListener.getAddedAddresses().contains(contactJID)); assertSame("Setup wrong name for the new contact!", contactName, addedEntry.getName()); assertSame("Setup wrong default subscription status!", ItemType.none, addedEntry.getType()); assertSame("The new contact shouldn't be member of any group!", 0, addedEntry.getGroups().size()); // Verify the unchanged roster items verifyRomeosEntry(roster.getEntry(JidCreate.entityBareFrom("romeo@example.net"))); verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries.", 4, roster.getEntries().size()); } /** * Test processing a roster push with an empty group is equivalent with providing * no group. * * @see SMACK-294 */ @Test public void testEmptyGroupRosterPush() throws Throwable { final BareJid contactJID = JidCreate.entityBareFrom("nurse@example.com"); assertNotNull("Can't get the roster from the provided connection!", roster); final StringBuilder sb = new StringBuilder(); sb.append("") .append("") .append("") .append("") .append("") .append("") .append(""); final XmlPullParser parser = TestUtils.getIQParser(sb.toString()); final IQ rosterPush = PacketParserUtils.parseIQ(parser); initRoster(); rosterListener.reset(); // Simulate receiving the roster push connection.processStanza(rosterPush); rosterListener.waitUntilInvocationOrTimeout(); // Verify the roster entry of the new contact final RosterEntry addedEntry = roster.getEntry(contactJID); assertNotNull("The new contact wasn't added to the roster!", addedEntry); assertTrue("The roster listener wasn't invoked for the new contact!", rosterListener.getAddedAddresses().contains(contactJID)); assertSame("Setup wrong default subscription status!", ItemType.none, addedEntry.getType()); assertSame("The new contact shouldn't be member of any group!", 0, addedEntry.getGroups().size()); // Verify the unchanged roster items verifyRomeosEntry(roster.getEntry(JidCreate.entityBareFrom("romeo@example.net"))); verifyMercutiosEntry(roster.getEntry(JidCreate.entityBareFrom("mercutio@example.com"))); verifyBenvoliosEntry(roster.getEntry(JidCreate.entityBareFrom("benvolio@example.net"))); assertSame("Wrong number of roster entries.", 4, roster.getEntries().size()); } /** * Remove all roster entries by iterating trough {@link Roster#getEntries()} * and simulating receiving roster pushes from the server. * * @param connection the dummy connection of which the provided roster belongs to. * @param roster the roster (or buddy list) which should be initialized. */ public static void removeAllRosterEntries(DummyConnection connection, Roster roster) { for (RosterEntry entry : roster.getEntries()) { // prepare the roster push packet final RosterPacket rosterPush = new RosterPacket(); rosterPush.setType(Type.set); rosterPush.setTo(connection.getUser()); // prepare the buddy's item entry which should be removed final RosterPacket.Item item = new RosterPacket.Item(entry.getJid(), entry.getName()); item.setItemType(ItemType.remove); rosterPush.addRosterItem(item); // simulate receiving the roster push connection.processStanza(rosterPush); } } /** * Initialize the roster according to the example in * RFC3921: Retrieving One's Roster on Login. * * @throws SmackException * @throws XmppStringprepException */ private void initRoster() throws InterruptedException, SmackException, XmppStringprepException { roster.reload(); while (true) { final Stanza sentPacket = connection.getSentPacket(); if (sentPacket instanceof RosterPacket && ((IQ) sentPacket).getType() == Type.get) { // setup the roster get request final RosterPacket rosterRequest = (RosterPacket) sentPacket; assertSame("The element MUST NOT contain any child elements!", 0, rosterRequest.getRosterItemCount()); // prepare the roster result final RosterPacket rosterResult = new RosterPacket(); rosterResult.setTo(connection.getUser()); rosterResult.setType(Type.result); rosterResult.setStanzaId(rosterRequest.getStanzaId()); // prepare romeo's roster entry final Item romeo = new Item(JidCreate.entityBareFrom("romeo@example.net"), "Romeo"); romeo.addGroupName("Friends"); romeo.setItemType(ItemType.both); rosterResult.addRosterItem(romeo); // prepare mercutio's roster entry final Item mercutio = new Item(JidCreate.entityBareFrom("mercutio@example.com"), "Mercutio"); mercutio.setItemType(ItemType.from); rosterResult.addRosterItem(mercutio); // prepare benvolio's roster entry final Item benvolio = new Item(JidCreate.entityBareFrom("benvolio@example.net"), "Benvolio"); benvolio.setItemType(ItemType.both); rosterResult.addRosterItem(benvolio); // simulate receiving the roster result and exit the loop connection.processStanza(rosterResult); break; } } roster.waitUntilLoaded(); rosterListener.waitUntilInvocationOrTimeout(); } /** * Check Romeo's roster entry according to the example in * RFC3921: Retrieving One's Roster on Login. * * @param romeo the roster entry which should be verified. */ public static void verifyRomeosEntry(final RosterEntry romeo) { assertNotNull("Can't get Romeo's roster entry!", romeo); assertSame("Setup wrong name for Romeo!", "Romeo", romeo.getName()); assertSame("Setup wrong subscription status for Romeo!", ItemType.both, romeo.getType()); assertSame("Romeo should be member of exactly one group!", 1, romeo.getGroups().size()); assertSame("Setup wrong group name for Romeo!", "Friends", romeo.getGroups().iterator().next().getName()); } /** * Check Mercutio's roster entry according to the example in * RFC3921: Retrieving One's Roster on Login. * * @param mercutio the roster entry which should be verified. */ public static void verifyMercutiosEntry(final RosterEntry mercutio) { assertNotNull("Can't get Mercutio's roster entry!", mercutio); assertSame("Setup wrong name for Mercutio!", "Mercutio", mercutio.getName()); assertSame("Setup wrong subscription status for Mercutio!", ItemType.from, mercutio.getType()); assertTrue("Mercutio shouldn't be a member of any group!", mercutio.getGroups().isEmpty()); } /** * Check Benvolio's roster entry according to the example in * RFC3921: Retrieving One's Roster on Login. * * @param benvolio the roster entry which should be verified. */ public static void verifyBenvoliosEntry(final RosterEntry benvolio) { assertNotNull("Can't get Benvolio's roster entry!", benvolio); assertSame("Setup wrong name for Benvolio!", "Benvolio", benvolio.getName()); assertSame("Setup wrong subscription status for Benvolio!", ItemType.both, benvolio.getType()); assertTrue("Benvolio shouldn't be a member of any group!", benvolio.getGroups().isEmpty()); } /** * This class can be used to simulate the server response for * a roster update request. */ private abstract class RosterUpdateResponder extends Thread { private Throwable exception = null; /** * Overwrite this method to check if the received update request is valid. * * @param updateRequest the request which would be sent to the server. */ abstract void verifyUpdateRequest(RosterPacket updateRequest); @Override public void run() { try { while (true) { final Stanza packet = connection.getSentPacket(); if (packet instanceof RosterPacket && ((IQ) packet).getType() == Type.set) { final RosterPacket rosterRequest = (RosterPacket) packet; // Prepare and process the roster push final RosterPacket rosterPush = new RosterPacket(); final Item item = rosterRequest.getRosterItems().iterator().next(); if (item.getItemType() != ItemType.remove) { item.setItemType(ItemType.none); } rosterPush.setType(Type.set); rosterPush.setTo(connection.getUser()); rosterPush.addRosterItem(item); connection.processStanza(rosterPush); // Create and process the IQ response final IQ response = IQ.createResultIQ(rosterRequest); connection.processStanza(response); // Verify the roster update request if (rosterRequest.getRosterItemCount() != 1) { throw new AssertionError("A roster set MUST contain one and only one element."); } verifyUpdateRequest(rosterRequest); break; } } } catch (Throwable e) { exception = e; fail(e.getMessage()); } } /** * Returns the exception or error if something went wrong. * * @return the Throwable exception or error that occurred. */ public Throwable getException() { return exception; } } /** * This class can be used to check if the RosterListener was invoked. */ public static class TestRosterListener extends WaitForPacketListener implements RosterListener { private final List addressesAdded = new CopyOnWriteArrayList<>(); private final List addressesDeleted = new CopyOnWriteArrayList<>(); private final List addressesUpdated = new CopyOnWriteArrayList<>(); @Override public synchronized void entriesAdded(Collection addresses) { addressesAdded.addAll(addresses); reportInvoked(); } @Override public synchronized void entriesDeleted(Collection addresses) { addressesDeleted.addAll(addresses); reportInvoked(); } @Override public synchronized void entriesUpdated(Collection addresses) { addressesUpdated.addAll(addresses); reportInvoked(); } @Override public void presenceChanged(Presence presence) { reportInvoked(); } /** * Get a collection of JIDs of the added roster items. * * @return the collection of addresses which were added. */ public Collection getAddedAddresses() { return Collections.unmodifiableCollection(addressesAdded); } /** * Get a collection of JIDs of the deleted roster items. * * @return the collection of addresses which were deleted. */ public Collection getDeletedAddresses() { return Collections.unmodifiableCollection(addressesDeleted); } /** * Get a collection of JIDs of the updated roster items. * * @return the collection of addresses which were updated. */ public Collection getUpdatedAddresses() { return Collections.unmodifiableCollection(addressesUpdated); } public boolean addedAddressesContains(String jidString) { Jid jid; try { jid = JidCreate.from(jidString); } catch (XmppStringprepException e) { throw new IllegalArgumentException(e); } return addressesAdded.contains(jid); } public boolean deletedAddressesContains(String jidString) { Jid jid; try { jid = JidCreate.from(jidString); } catch (XmppStringprepException e) { throw new IllegalArgumentException(e); } return addressesDeleted.contains(jid); } public boolean updatedAddressesContains(String jidString) { Jid jid; try { jid = JidCreate.from(jidString); } catch (XmppStringprepException e) { throw new IllegalArgumentException(e); } return addressesUpdated.contains(jid); } /** * Reset the lists of added, deleted or updated items. */ @Override public synchronized void reset() { super.reset(); addressesAdded.clear(); addressesDeleted.clear(); addressesUpdated.clear(); } } }