/* * Jitsi, the OpenSource Java VoIP and Instant Messaging client. * * Copyright @ 2015 Atlassian Pty Ltd * * 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 net.java.sip.communicator.service.protocol; import java.util.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.util.*; /** * Represents a default implementation of * <tt>OperationSetContactCapabilities</tt> which attempts to make it easier for * implementers to provide complete solutions while focusing on * implementation-specific functionality. * * @param <T> the type of the <tt>ProtocolProviderService</tt> implementation * providing the <tt>AbstractOperationSetContactCapabilities</tt> implementation * * @author Lyubomir Marinov */ public abstract class AbstractOperationSetContactCapabilities< T extends ProtocolProviderService> implements OperationSetContactCapabilities { /** * The <tt>Logger</tt> used by the * <tt>AbstractOperationSetContactCapabilities</tt> class and its instances * for logging output. */ private static final Logger logger = Logger.getLogger(AbstractOperationSetContactCapabilities.class); /** * The list of <tt>ContactCapabilitiesListener</tt>s registered to be * notified about changes in the list of <tt>OperationSet</tt> capabilities * of <tt>Contact</tt>s. */ private final List<ContactCapabilitiesListener> contactCapabilitiesListeners = new LinkedList<ContactCapabilitiesListener>(); /** * The <tt>ProtocolProviderService</tt> which provides this * <tt>OperationSetContactCapabilities</tt>. */ protected final T parentProvider; /** * Initializes a new <tt>AbstractOperationSetContactCapabilities</tt> * instance which is to be provided by a specific * <tt>ProtocolProviderService</tt> implementation. * * @param parentProvider the <tt>ProtocolProviderService</tt> implementation * which will provide the new instance */ protected AbstractOperationSetContactCapabilities(T parentProvider) { if (parentProvider == null) throw new NullPointerException("parentProvider"); this.parentProvider = parentProvider; } /** * Registers a specific <tt>ContactCapabilitiesListener</tt> to be notified * about changes in the list of <tt>OperationSet</tt> capabilities of * <tt>Contact</tt>s. If the specified <tt>listener</tt> has already been * registered, adding it again has no effect. * * @param listener the <tt>ContactCapabilitiesListener</tt> which is to be * notified about changes in the list of <tt>OperationSet</tt> capabilities * of <tt>Contact</tt>s * @see OperationSetContactCapabilities#addContactCapabilitiesListener( * ContactCapabilitiesListener) */ public void addContactCapabilitiesListener( ContactCapabilitiesListener listener) { if (listener == null) throw new NullPointerException("listener"); synchronized (contactCapabilitiesListeners) { if (!contactCapabilitiesListeners.contains(listener)) contactCapabilitiesListeners.add(listener); } } /** * Fires a new <tt>ContactCapabilitiesEvent</tt> to notify the registered * <tt>ContactCapabilitiesListener</tt>s that a specific <tt>Contact</tt> * has changed its list of <tt>OperationSet</tt> capabilities. * * @param sourceContact the <tt>Contact</tt> which is the source/cause of * the event to be fired * @param eventID the ID of the event to be fired which indicates the * specifics of the change of the list of <tt>OperationSet</tt> capabilities * of the specified <tt>sourceContact</tt> and the details of the event * @param opSets the new set of operation sets for the given source contact */ protected void fireContactCapabilitiesEvent( Contact sourceContact, int eventID, Map<String, ? extends OperationSet> opSets) { ContactCapabilitiesListener[] listeners; synchronized (contactCapabilitiesListeners) { listeners = contactCapabilitiesListeners.toArray( new ContactCapabilitiesListener[ contactCapabilitiesListeners.size()]); } if (listeners.length != 0) { ContactCapabilitiesEvent event = new ContactCapabilitiesEvent(sourceContact, eventID, opSets); for (ContactCapabilitiesListener listener : listeners) { switch (eventID) { case ContactCapabilitiesEvent.SUPPORTED_OPERATION_SETS_CHANGED: listener.supportedOperationSetsChanged(event); break; default: if (logger.isDebugEnabled()) { logger.debug( "Cannot fire ContactCapabilitiesEvent with" + " unsupported eventID: " + eventID); } throw new IllegalArgumentException("eventID"); } } } } /** * Gets the <tt>OperationSet</tt> corresponding to the specified * <tt>Class</tt> and supported by the specified <tt>Contact</tt>. If the * returned value is non-<tt>null</tt>, it indicates that the * <tt>Contact</tt> is considered by the associated protocol provider to * possess the <tt>opsetClass</tt> capability. Otherwise, the associated * protocol provider considers <tt>contact</tt> to not have the * <tt>opsetClass</tt> capability. * <tt>AbstractOperationSetContactCapabilities</tt> looks for the name of * the specified <tt>opsetClass</tt> in the <tt>Map</tt> returned by * {@link #getSupportedOperationSets(Contact)} and returns the associated * <tt>OperationSet</tt>. Since the implementation is suboptimal due to the * temporary <tt>Map</tt> allocations and lookups, extenders are advised to * override {@link #getOperationSet(Contact, Class, boolean)}. * * @param <U> the type extending <tt>OperationSet</tt> for which the * specified <tt>contact</tt> is to be checked whether it possesses it as a * capability * @param contact the <tt>Contact</tt> for which the <tt>opsetClass</tt> * capability is to be queried * @param opsetClass the <tt>OperationSet</tt> <tt>Class</tt> for which the * specified <tt>contact</tt> is to be checked whether it possesses it as a * capability * @return the <tt>OperationSet</tt> corresponding to the specified * <tt>opsetClass</tt> which is considered by the associated protocol * provider to be possessed as a capability by the specified * <tt>contact</tt>; otherwise, <tt>null</tt> * @see OperationSetContactCapabilities#getOperationSet(Contact, Class) */ public <U extends OperationSet> U getOperationSet( Contact contact, Class<U> opsetClass) { return getOperationSet(contact, opsetClass, isOnline(contact)); } /** * Gets the <tt>OperationSet</tt> corresponding to the specified * <tt>Class</tt> and supported by the specified <tt>Contact</tt>. If the * returned value is non-<tt>null</tt>, it indicates that the * <tt>Contact</tt> is considered by the associated protocol provider to * possess the <tt>opsetClass</tt> capability. Otherwise, the associated * protocol provider considers <tt>contact</tt> to not have the * <tt>opsetClass</tt> capability. * <tt>AbstractOperationSetContactCapabilities</tt> looks for the name of * the specified <tt>opsetClass</tt> in the <tt>Map</tt> returned by * {@link #getSupportedOperationSets(Contact)} and returns the associated * <tt>OperationSet</tt>. Since the implementation is suboptimal due to the * temporary <tt>Map</tt> allocations and lookups, extenders are advised to * override. * * @param <U> the type extending <tt>OperationSet</tt> for which the * specified <tt>contact</tt> is to be checked whether it possesses it as a * capability * @param contact the <tt>Contact</tt> for which the <tt>opsetClass</tt> * capability is to be queried * @param opsetClass the <tt>OperationSet</tt> <tt>Class</tt> for which the * specified <tt>contact</tt> is to be checked whether it possesses it as a * capability * @param online <tt>true</tt> if <tt>contact</tt> is online; otherwise, * <tt>false</tt> * @return the <tt>OperationSet</tt> corresponding to the specified * <tt>opsetClass</tt> which is considered by the associated protocol * provider to be possessed as a capability by the specified * <tt>contact</tt>; otherwise, <tt>null</tt> * @see OperationSetContactCapabilities#getOperationSet(Contact, Class) */ @SuppressWarnings("unchecked") protected <U extends OperationSet> U getOperationSet( Contact contact, Class<U> opsetClass, boolean online) { Map<String, OperationSet> supportedOperationSets = getSupportedOperationSets(contact, online); if (supportedOperationSets != null) { OperationSet opset = supportedOperationSets.get(opsetClass.getName()); if (opsetClass.isInstance(opset)) return (U) opset; } return null; } /** * Gets the <tt>OperationSet</tt>s supported by a specific <tt>Contact</tt>. * The returned <tt>OperationSet</tt>s are considered by the associated * protocol provider to capabilities possessed by the specified * <tt>contact</tt>. The default implementation returns the result of * calling {@link ProtocolProviderService#getSupportedOperationSets()} on * the associated <tt>ProtocolProviderService</tt> implementation. Extenders * have to override the default implementation of * {@link #getSupportedOperationSets(Contact, boolean)} in order to provide * actual capability detection for the specified <tt>contact</tt>. * * @param contact the <tt>Contact</tt> for which the supported * <tt>OperationSet</tt> capabilities are to be retrieved * @return a <tt>Map</tt> listing the <tt>OperationSet</tt>s considered by * the associated protocol provider to be supported by the specified * <tt>contact</tt> (i.e. to be possessed as capabilities). Each supported * <tt>OperationSet</tt> capability is represented by a <tt>Map.Entry</tt> * with key equal to the <tt>OperationSet</tt> class name and value equal to * the respective <tt>OperationSet</tt> instance * @see OperationSetContactCapabilities#getSupportedOperationSets(Contact) */ public Map<String, OperationSet> getSupportedOperationSets(Contact contact) { return getSupportedOperationSets(contact, isOnline(contact)); } /** * Gets the <tt>OperationSet</tt>s supported by a specific <tt>Contact</tt>. * The returned <tt>OperationSet</tt>s are considered by the associated * protocol provider to capabilities possessed by the specified * <tt>contact</tt>. The default implementation returns the result of * calling {@link ProtocolProviderService#getSupportedOperationSets()} on * the associated <tt>ProtocolProviderService</tt> implementation. Extenders * have to override the default implementation in order to provide actual * capability detection for the specified <tt>contact</tt>. * * @param contact the <tt>Contact</tt> for which the supported * <tt>OperationSet</tt> capabilities are to be retrieved * @param online <tt>true</tt> if <tt>contact</tt> is online; otherwise, * <tt>false</tt> * @return a <tt>Map</tt> listing the <tt>OperationSet</tt>s considered by * the associated protocol provider to be supported by the specified * <tt>contact</tt> (i.e. to be possessed as capabilities). Each supported * <tt>OperationSet</tt> capability is represented by a <tt>Map.Entry</tt> * with key equal to the <tt>OperationSet</tt> class name and value equal to * the respective <tt>OperationSet</tt> instance * @see OperationSetContactCapabilities#getSupportedOperationSets(Contact) */ protected Map<String, OperationSet> getSupportedOperationSets( Contact contact, boolean online) { return parentProvider.getSupportedOperationSets(); } /** * Determines whether a specific <tt>Contact</tt> is online (in contrast to * offline). * * @param contact the <tt>Contact</tt> which is to be determines whether it * is online * @return <tt>true</tt> if the specified <tt>contact</tt> is online; * otherwise, <tt>false</tt> */ protected boolean isOnline(Contact contact) { OperationSetPresence opsetPresence = parentProvider.getOperationSet(OperationSetPresence.class); if (opsetPresence == null) { /* * Presence is not implemented so we cannot really know and thus * we'll give it the benefit of the doubt and declare it online. */ return true; } else { PresenceStatus presenceStatus = null; Throwable exception = null; try { presenceStatus = opsetPresence.queryContactStatus(contact.getAddress()); } catch (IllegalArgumentException iaex) { exception = iaex; } catch (IllegalStateException isex) { exception = isex; } catch (OperationFailedException ofex) { exception = ofex; } if (presenceStatus == null) presenceStatus = contact.getPresenceStatus(); if (presenceStatus == null) { if ((exception != null) && logger.isDebugEnabled()) { logger.debug( "Failed to query PresenceStatus of Contact " + contact, exception); } /* * For whatever reason the PresenceStatus wasn't retrieved, it's * a fact that presence was advertised and the contacts wasn't * reported online. */ return false; } else return presenceStatus.isOnline(); } } /** * Unregisters a specific <tt>ContactCapabilitiesListener</tt> to no longer * be notified about changes in the list of <tt>OperationSet</tt> * capabilities of <tt>Contact</tt>s. If the specified <tt>listener</tt> has * already been unregistered or has never been registered, removing it has * no effect. * * @param listener the <tt>ContactCapabilitiesListener</tt> which is to no * longer be notified about changes in the list of <tt>OperationSet</tt> * capabilities of <tt>Contact</tt>s * @see OperationSetContactCapabilities#removeContactCapabilitiesListener( * ContactCapabilitiesListener) */ public void removeContactCapabilitiesListener( ContactCapabilitiesListener listener) { if (listener != null) { synchronized (contactCapabilitiesListeners) { contactCapabilitiesListeners.remove(listener); } } } }