/* * Kontalk Java client * Copyright (C) 2016 Kontalk Devteam <devteam@kontalk.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kontalk.model; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Observable; import java.util.Optional; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; import org.kontalk.misc.JID; import org.kontalk.persistence.Database; /** * Global list of all contacts. * * Does not contain deleted contacts. * * @author Alexander Bikadorov {@literal <bikaejkb@mail.tu-berlin.de>} */ public final class ContactList extends Observable { private static final Logger LOGGER = Logger.getLogger(ContactList.class.getName()); private enum ViewChange { MODIFIED } private final Map<JID, Contact> mJIDMap = Collections.synchronizedMap(new HashMap<JID, Contact>()); ContactList() {} Map<Integer, Contact> load() { assert mJIDMap.isEmpty(); Map<Integer, Contact> contactMap = new HashMap<>(); Database db = Model.database(); try (ResultSet resultSet = db.execSelectAll(Contact.TABLE)) { while (resultSet.next()) { Contact contact = Contact.load(resultSet); JID jid = contact.getJID(); if (mJIDMap.containsKey(jid)) { LOGGER.warning("contacts with equal JIDs: " + jid); continue; } if (!contact.isDeleted()) mJIDMap.put(jid, contact); contactMap.put(contact.getID(), contact); } } catch (SQLException ex) { LOGGER.log(Level.WARNING, "can't load contacts from db", ex); } this.changed(null); return contactMap; } /** Create and add a new contact. */ public Optional<Contact> create(JID jid, String name) { jid = jid.toBare(); if (!this.isValid(jid)) return Optional.empty(); Contact newContact = new Contact(jid, name); if (newContact.getID() < 1) return Optional.empty(); mJIDMap.put(newContact.getJID(), newContact); this.changed(ViewChange.MODIFIED); return Optional.of(newContact); } /** * Get the contact for a JID (if the JID is in the list). * Resource is removed for lookup. */ public Optional<Contact> get(JID jid) { return Optional.ofNullable(mJIDMap.get(jid)); } /** * Get the contact that represents the user itself. */ public Optional<Contact> getMe() { JID myJID = Model.getUserJID(); if (!myJID.isValid()) return Optional.empty(); return this.get(myJID); } public Set<Contact> getAll(boolean withMe, boolean blocked) { synchronized(mJIDMap) { return Collections.unmodifiableSet( mJIDMap.values().stream() .filter(c -> (blocked || !c.isBlocked()) && (withMe || !c.isMe())) .collect(Collectors.toSet())); } } public void delete(Contact contact) { boolean removed = mJIDMap.remove(contact.getJID(), contact); if (!removed) { LOGGER.warning("can't find contact "+contact); } contact.setDeleted(); this.changed(ViewChange.MODIFIED); } void onShutDown() { mJIDMap.values().forEach(Contact::onShutDown); } /** * Return whether a contact with a specified JID exists. */ public boolean contains(JID jid) { return mJIDMap.containsKey(jid); } public boolean changeJID(Contact contact, JID jid) { if (!this.isValid(jid)) return false; mJIDMap.put(jid, contact); mJIDMap.remove(contact.getJID()); contact.setJID(jid); return true; } private boolean isValid(JID jid) { if (!jid.isValid()) { LOGGER.warning("invalid jid: " + jid); return false; } if (mJIDMap.containsKey(jid)) { LOGGER.warning("jid already exists: "+jid); return false; } return true; } private void changed(ViewChange change) { this.setChanged(); this.notifyObservers(change); } }