示例#1
0
/**
 * The <tt>MUCServiceImpl</tt> class implements the service for the chat rooms.
 *
 * @author Hristo Terezov
 */
public class MUCServiceImpl extends MUCService {

  /** The list of persistent chat rooms. */
  private final ChatRoomListImpl chatRoomList = new ChatRoomListImpl();

  /**
   * The <tt>Logger</tt> used by the <tt>MUCServiceImpl</tt> class and its instances for logging
   * output.
   */
  private static Logger logger = Logger.getLogger(MUCServiceImpl.class);

  /**
   * Called to accept an incoming invitation. Adds the invitation chat room to the list of chat
   * rooms and joins it.
   *
   * @param invitation the invitation to accept.
   */
  public void acceptInvitation(ChatRoomInvitation invitation) {
    ChatRoom chatRoom = invitation.getTargetChatRoom();
    byte[] password = invitation.getChatRoomPassword();

    String nickName = chatRoom.getParentProvider().getAccountID().getUserID();

    joinChatRoom(chatRoom, nickName, password);
  }

  /**
   * Adds a change listener to the <tt>ChatRoomList</tt>.
   *
   * @param l the listener.
   */
  public void addChatRoomListChangeListener(ChatRoomListChangeListener l) {
    chatRoomList.addChatRoomListChangeListener(l);
  }

  /**
   * Removes a change listener to the <tt>ChatRoomList</tt>.
   *
   * @param l the listener.
   */
  public void removeChatRoomListChangeListener(ChatRoomListChangeListener l) {
    chatRoomList.removeChatRoomListChangeListener(l);
  }

  /**
   * Fires a <tt>ChatRoomListChangedEvent</tt> event.
   *
   * @param chatRoomWrapper the chat room.
   * @param eventID the id of the event.
   */
  public void fireChatRoomListChangedEvent(ChatRoomWrapper chatRoomWrapper, int eventID) {
    chatRoomList.fireChatRoomListChangedEvent(chatRoomWrapper, eventID);
  }

  /**
   * Joins the given chat room with the given password and manages all the exceptions that could
   * occur during the join process.
   *
   * @param chatRoomWrapper the chat room to join.
   * @param nickName the nickname we choose for the given chat room.
   * @param password the password.
   * @param rememberPassword if true the password should be saved.
   * @param isFirstAttempt is this the first attempt to join room, used to check whether to show
   *     some error messages
   * @param subject the subject which will be set to the room after the user join successful.
   */
  private void joinChatRoom(
      ChatRoomWrapper chatRoomWrapper,
      String nickName,
      byte[] password,
      boolean rememberPassword,
      boolean isFirstAttempt,
      String subject) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));
      return;
    }

    new JoinChatRoomTask(
            (ChatRoomWrapperImpl) chatRoomWrapper,
            nickName,
            password,
            rememberPassword,
            isFirstAttempt,
            subject)
        .start();
  }

  /**
   * Joins the given chat room with the given password and manages all the exceptions that could
   * occur during the join process.
   *
   * @param chatRoomWrapper the chat room to join.
   * @param nickName the nickname we choose for the given chat room.
   * @param password the password.
   */
  public void joinChatRoom(ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));
      return;
    }

    new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, nickName, password).start();
  }

  /**
   * Joins the given chat room with the given password and manages all the exceptions that could
   * occur during the join process.
   *
   * @param chatRoomWrapper the chat room to join.
   * @param nickName the nickname we choose for the given chat room.
   * @param password the password.
   * @param subject the subject which will be set to the room after the user join successful.
   */
  public void joinChatRoom(
      ChatRoomWrapper chatRoomWrapper, String nickName, byte[] password, String subject) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));

      return;
    }

    // join from add chat room dialog

    new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, nickName, password, subject)
        .start();
  }

  /**
   * Join chat room.
   *
   * @param chatRoomWrapper
   */
  public void joinChatRoom(ChatRoomWrapper chatRoomWrapper) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.WARNING"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_CONNECTED",
                      new String[] {chatRoomWrapper.getChatRoomName()}));

      return;
    }

    new JoinChatRoomTask((ChatRoomWrapperImpl) chatRoomWrapper, null, null).start();
  }

  /**
   * Joins the given chat room and manages all the exceptions that could occur during the join
   * process.
   *
   * @param chatRoom the chat room to join
   * @param nickname the nickname we're using to join
   * @param password the password we're using to join
   */
  public void joinChatRoom(ChatRoom chatRoom, String nickname, byte[] password) {
    ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

    if (chatRoomWrapper == null) {
      ChatRoomProviderWrapper parentProvider =
          chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider());

      chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);

      chatRoomList.addChatRoom(chatRoomWrapper);

      fireChatRoomListChangedEvent(chatRoomWrapper, ChatRoomListChangeEvent.CHAT_ROOM_ADDED);
    }

    this.joinChatRoom(chatRoomWrapper, nickname, password);
  }

  /**
   * Joins the room with the given name though the given chat room provider.
   *
   * @param chatRoomName the name of the room to join.
   * @param chatRoomProvider the chat room provider to join through.
   */
  public void joinChatRoom(String chatRoomName, ChatRoomProviderWrapper chatRoomProvider) {
    OperationSetMultiUserChat groupChatOpSet =
        chatRoomProvider.getProtocolProvider().getOperationSet(OperationSetMultiUserChat.class);

    ChatRoom chatRoom = null;
    try {
      chatRoom = groupChatOpSet.findRoom(chatRoomName);
    } catch (Exception e) {
      if (logger.isTraceEnabled())
        logger.trace("Un exception occurred while searching for room:" + chatRoomName, e);
    }

    if (chatRoom != null) {
      ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

      if (chatRoomWrapper == null) {
        ChatRoomProviderWrapper parentProvider =
            chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider());

        chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);

        chatRoomList.addChatRoom(chatRoomWrapper);

        fireChatRoomListChangedEvent(chatRoomWrapper, ChatRoomListChangeEvent.CHAT_ROOM_ADDED);
      }
      joinChatRoom(chatRoomWrapper);
    } else
      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.ERROR"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CHAT_ROOM_NOT_EXIST",
                      new String[] {
                        chatRoomName,
                        chatRoomProvider.getProtocolProvider().getAccountID().getService()
                      }));
  }

  /**
   * Creates a chat room, by specifying the chat room name, the parent protocol provider and
   * eventually, the contacts invited to participate in this chat room.
   *
   * @param roomName the name of the room
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param persistent is the room persistent
   * @param isPrivate whether the room will be private or public.
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createChatRoom(
      String roomName,
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean persistent,
      boolean isPrivate) {
    return createChatRoom(
        roomName, protocolProvider, contacts, reason, true, persistent, isPrivate);
  }

  /**
   * Creates a chat room, by specifying the chat room name, the parent protocol provider and
   * eventually, the contacts invited to participate in this chat room.
   *
   * @param roomName the name of the room
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param persistent is the room persistent
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createChatRoom(
      String roomName,
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean persistent) {
    return createChatRoom(roomName, protocolProvider, contacts, reason, true, persistent, false);
  }

  /**
   * Creates a chat room, by specifying the chat room name, the parent protocol provider and
   * eventually, the contacts invited to participate in this chat room.
   *
   * @param roomName the name of the room
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param join whether we should join the room after creating it.
   * @param persistent whether the newly created room will be persistent.
   * @param isPrivate whether the room will be private or public.
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createChatRoom(
      String roomName,
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean join,
      boolean persistent,
      boolean isPrivate) {
    ChatRoomWrapper chatRoomWrapper = null;
    OperationSetMultiUserChat groupChatOpSet =
        protocolProvider.getOperationSet(OperationSetMultiUserChat.class);

    // If there's no group chat operation set we have nothing to do here.
    if (groupChatOpSet == null) return null;

    ChatRoom chatRoom = null;
    try {

      HashMap<String, Object> roomProperties = new HashMap<String, Object>();
      roomProperties.put("isPrivate", isPrivate);
      chatRoom = groupChatOpSet.createChatRoom(roomName, roomProperties);

      if (join) {
        chatRoom.join();

        for (String contact : contacts) chatRoom.invite(contact, reason);
      }
    } catch (OperationFailedException ex) {
      logger.error("Failed to create chat room.", ex);

      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.ERROR"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CREATE_CHAT_ROOM_ERROR",
                      new String[] {protocolProvider.getProtocolDisplayName()}),
              ex);
    } catch (OperationNotSupportedException ex) {
      logger.error("Failed to create chat room.", ex);

      MUCActivator.getAlertUIService()
          .showAlertDialog(
              MUCActivator.getResources().getI18NString("service.gui.ERROR"),
              MUCActivator.getResources()
                  .getI18NString(
                      "service.gui.CREATE_CHAT_ROOM_ERROR",
                      new String[] {protocolProvider.getProtocolDisplayName()}),
              ex);
    }

    if (chatRoom != null) {
      ChatRoomProviderWrapper parentProvider =
          chatRoomList.findServerWrapperFromProvider(protocolProvider);

      // if there is the same room ids don't add new wrapper as old one
      // maybe already created
      chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

      if (chatRoomWrapper == null) {
        chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);
        chatRoomWrapper.setPersistent(persistent);
        chatRoomList.addChatRoom(chatRoomWrapper);
      }
    }

    return chatRoomWrapper;
  }

  /**
   * Creates a private chat room, by specifying the parent protocol provider and eventually, the
   * contacts invited to participate in this chat room.
   *
   * @param protocolProvider the parent protocol provider.
   * @param contacts the contacts invited when creating the chat room.
   * @param reason
   * @param persistent is the room persistent
   * @return the <tt>ChatRoomWrapper</tt> corresponding to the created room
   */
  public ChatRoomWrapper createPrivateChatRoom(
      ProtocolProviderService protocolProvider,
      Collection<String> contacts,
      String reason,
      boolean persistent) {
    return this.createChatRoom(null, protocolProvider, contacts, reason, persistent, true);
  }

  /**
   * Returns existing chat rooms for the given <tt>chatRoomProvider</tt>.
   *
   * @param chatRoomProvider the <tt>ChatRoomProviderWrapper</tt>, which chat rooms we're looking
   *     for
   * @return existing chat rooms for the given <tt>chatRoomProvider</tt>
   */
  public List<String> getExistingChatRooms(ChatRoomProviderWrapper chatRoomProvider) {
    if (chatRoomProvider == null) return null;

    ProtocolProviderService protocolProvider = chatRoomProvider.getProtocolProvider();

    if (protocolProvider == null) return null;

    OperationSetMultiUserChat groupChatOpSet =
        protocolProvider.getOperationSet(OperationSetMultiUserChat.class);

    if (groupChatOpSet == null) return null;

    List<String> chatRooms = null;
    try {
      chatRooms = groupChatOpSet.getExistingChatRooms();
    } catch (OperationFailedException e) {
      if (logger.isTraceEnabled())
        logger.trace(
            "Failed to obtain existing chat rooms for server: "
                + protocolProvider.getAccountID().getService(),
            e);
    } catch (OperationNotSupportedException e) {
      if (logger.isTraceEnabled())
        logger.trace(
            "Failed to obtain existing chat rooms for server: "
                + protocolProvider.getAccountID().getService(),
            e);
    }

    return chatRooms;
  }

  /**
   * Rejects the given invitation with the specified reason.
   *
   * @param multiUserChatOpSet the operation set to use for rejecting the invitation
   * @param invitation the invitation to reject
   * @param reason the reason for the rejection
   */
  public void rejectInvitation(
      OperationSetMultiUserChat multiUserChatOpSet, ChatRoomInvitation invitation, String reason) {
    multiUserChatOpSet.rejectInvitation(invitation, reason);
  }

  /**
   * Leaves the given chat room.
   *
   * @param chatRoomWrapper the chat room to leave.
   * @return <tt>ChatRoomWrapper</tt> instance associated with the chat room.
   */
  public ChatRoomWrapper leaveChatRoom(ChatRoomWrapper chatRoomWrapper) {
    ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

    if (chatRoom == null) {
      ResourceManagementService resources = MUCActivator.getResources();

      MUCActivator.getAlertUIService()
          .showAlertDialog(
              resources.getI18NString("service.gui.WARNING"),
              resources.getI18NString("service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED"));

      return null;
    }

    if (chatRoom.isJoined()) chatRoom.leave();

    ChatRoomWrapper existChatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

    if (existChatRoomWrapper == null) return null;

    // We save the choice of the user, before the chat room is really
    // joined, because even the join fails we want the next time when
    // we login to join this chat room automatically.
    ConfigurationUtils.updateChatRoomStatus(
        chatRoomWrapper.getParentProvider().getProtocolProvider(),
        chatRoomWrapper.getChatRoomID(),
        GlobalStatusEnum.OFFLINE_STATUS);

    return existChatRoomWrapper;
  }

  /** Joins a chat room in an asynchronous way. */
  private class JoinChatRoomTask extends Thread {
    private final ChatRoomWrapperImpl chatRoomWrapper;

    private final String nickName;

    private final byte[] password;

    private final boolean rememberPassword;

    private final boolean isFirstAttempt;

    private final String subject;

    private ResourceManagementService resources = MUCActivator.getResources();

    JoinChatRoomTask(
        ChatRoomWrapperImpl chatRoomWrapper,
        String nickName,
        byte[] password,
        boolean rememberPassword,
        boolean isFirstAttempt,
        String subject) {
      this.chatRoomWrapper = chatRoomWrapper;
      this.nickName = nickName;
      this.isFirstAttempt = isFirstAttempt;
      this.subject = subject;

      if (password == null) {
        String passString = chatRoomWrapper.loadPassword();
        if (passString != null) {
          this.password = passString.getBytes();
        } else {
          this.password = null;
        }
      } else {
        this.password = password;
      }
      this.rememberPassword = rememberPassword;
    }

    JoinChatRoomTask(ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password) {
      this(chatRoomWrapper, nickName, password, false, true, null);
    }

    JoinChatRoomTask(
        ChatRoomWrapperImpl chatRoomWrapper, String nickName, byte[] password, String subject) {
      this(chatRoomWrapper, nickName, password, false, true, subject);
    }

    /** @override {@link Thread}{@link #run()} to perform all asynchronous tasks. */
    @Override
    public void run() {
      ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

      try {
        if (password != null && password.length > 0) chatRoom.joinAs(nickName, password);
        else if (nickName != null) chatRoom.joinAs(nickName);
        else chatRoom.join();

        done(JOIN_SUCCESS_PROP);
      } catch (OperationFailedException e) {
        if (logger.isTraceEnabled())
          logger.trace("Failed to join chat room: " + chatRoom.getName(), e);

        switch (e.getErrorCode()) {
          case OperationFailedException.AUTHENTICATION_FAILED:
            done(JOIN_AUTHENTICATION_FAILED_PROP);
            break;
          case OperationFailedException.REGISTRATION_REQUIRED:
            done(JOIN_REGISTRATION_REQUIRED_PROP);
            break;
          case OperationFailedException.PROVIDER_NOT_REGISTERED:
            done(JOIN_PROVIDER_NOT_REGISTERED_PROP);
            break;
          case OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS:
            done(JOIN_SUBSCRIPTION_ALREADY_EXISTS_PROP);
            break;
          default:
            done(JOIN_UNKNOWN_ERROR_PROP);
        }
      }
    }

    /**
     * Performs UI changes after the chat room join task has finished.
     *
     * @param returnCode the result code from the chat room join task.
     */
    private void done(String returnCode) {
      ConfigurationUtils.updateChatRoomStatus(
          chatRoomWrapper.getParentProvider().getProtocolProvider(),
          chatRoomWrapper.getChatRoomID(),
          GlobalStatusEnum.ONLINE_STATUS);

      String errorMessage = null;
      if (JOIN_AUTHENTICATION_FAILED_PROP.equals(returnCode)) {
        chatRoomWrapper.removePassword();

        AuthenticationWindowService authWindowsService =
            ServiceUtils.getService(MUCActivator.bundleContext, AuthenticationWindowService.class);

        AuthenticationWindowService.AuthenticationWindow authWindow =
            authWindowsService.create(
                null,
                null,
                null,
                false,
                chatRoomWrapper.isPersistent(),
                AuthenticationWindow.getAuthenticationWindowIcon(
                    chatRoomWrapper.getParentProvider().getProtocolProvider()),
                resources.getI18NString(
                    "service.gui.AUTHENTICATION_WINDOW_TITLE",
                    new String[] {chatRoomWrapper.getParentProvider().getName()}),
                resources.getI18NString(
                    "service.gui.CHAT_ROOM_REQUIRES_PASSWORD",
                    new String[] {chatRoomWrapper.getChatRoomName()}),
                "",
                null,
                isFirstAttempt
                    ? null
                    : resources.getI18NString(
                        "service.gui.AUTHENTICATION_FAILED",
                        new String[] {chatRoomWrapper.getChatRoomName()}),
                null);

        authWindow.setVisible(true);

        if (!authWindow.isCanceled()) {
          joinChatRoom(
              chatRoomWrapper,
              nickName,
              new String(authWindow.getPassword()).getBytes(),
              authWindow.isRememberPassword(),
              false,
              subject);
        }
      } else if (JOIN_REGISTRATION_REQUIRED_PROP.equals(returnCode)) {
        errorMessage =
            resources.getI18NString(
                "service.gui.CHAT_ROOM_REGISTRATION_REQUIRED",
                new String[] {chatRoomWrapper.getChatRoomName()});
      } else if (JOIN_PROVIDER_NOT_REGISTERED_PROP.equals(returnCode)) {
        errorMessage =
            resources.getI18NString(
                "service.gui.CHAT_ROOM_NOT_CONNECTED",
                new String[] {chatRoomWrapper.getChatRoomName()});
      } else if (JOIN_SUBSCRIPTION_ALREADY_EXISTS_PROP.equals(returnCode)) {
        errorMessage =
            resources.getI18NString(
                "service.gui.CHAT_ROOM_ALREADY_JOINED",
                new String[] {chatRoomWrapper.getChatRoomName()});
      } else {
        errorMessage =
            resources.getI18NString(
                "service.gui.FAILED_TO_JOIN_CHAT_ROOM",
                new String[] {chatRoomWrapper.getChatRoomName()});
      }

      if (!JOIN_SUCCESS_PROP.equals(returnCode)
          && !JOIN_AUTHENTICATION_FAILED_PROP.equals(returnCode)) {
        MUCActivator.getAlertUIService()
            .showAlertPopup(resources.getI18NString("service.gui.ERROR"), errorMessage);
      }

      if (JOIN_SUCCESS_PROP.equals(returnCode)) {
        if (rememberPassword) {
          chatRoomWrapper.savePassword(new String(password));
        }

        if (subject != null) {
          try {
            chatRoomWrapper.getChatRoom().setSubject(subject);
          } catch (OperationFailedException ex) {
            logger.warn("Failed to set subject.");
          }
        }
      }

      chatRoomWrapper.firePropertyChange(returnCode);
    }
  }

  /**
   * Finds the <tt>ChatRoomWrapper</tt> instance associated with the source contact.
   *
   * @param contact the source contact.
   * @return the <tt>ChatRoomWrapper</tt> instance.
   */
  public ChatRoomWrapper findChatRoomWrapperFromSourceContact(SourceContact contact) {
    if (!(contact instanceof ChatRoomSourceContact)) return null;
    ChatRoomSourceContact chatRoomContact = (ChatRoomSourceContact) contact;
    return chatRoomList.findChatRoomWrapperFromChatRoomID(
        chatRoomContact.getChatRoomID(), chatRoomContact.getProvider());
  }

  /**
   * Finds the <tt>ChatRoomWrapper</tt> instance associated with the chat room.
   *
   * @param chatRoomID the id of the chat room.
   * @param pps the provider of the chat room.
   * @return the <tt>ChatRoomWrapper</tt> instance.
   */
  public ChatRoomWrapper findChatRoomWrapperFromChatRoomID(
      String chatRoomID, ProtocolProviderService pps) {
    return chatRoomList.findChatRoomWrapperFromChatRoomID(chatRoomID, pps);
  }

  /**
   * Searches for chat room wrapper in chat room list by chat room.
   *
   * @param chatRoom the chat room.
   * @param create if <tt>true</tt> and the chat room wrapper is not found new chatRoomWrapper is
   *     created.
   * @return found chat room wrapper or the created chat room wrapper.
   */
  @Override
  public ChatRoomWrapper getChatRoomWrapperByChatRoom(ChatRoom chatRoom, boolean create) {
    ChatRoomWrapper chatRoomWrapper = chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);

    if ((chatRoomWrapper == null) && create) {
      ChatRoomProviderWrapper parentProvider =
          chatRoomList.findServerWrapperFromProvider(chatRoom.getParentProvider());

      chatRoomWrapper = new ChatRoomWrapperImpl(parentProvider, chatRoom);

      chatRoomList.addChatRoom(chatRoomWrapper);
    }
    return chatRoomWrapper;
  }

  /**
   * Goes through the locally stored chat rooms list and for each {@link ChatRoomWrapper} tries to
   * find the corresponding server stored {@link ChatRoom} in the specified operation set. Joins
   * automatically all found chat rooms.
   *
   * @param protocolProvider the protocol provider for the account to synchronize
   * @param opSet the multi user chat operation set, which give us access to chat room server
   */
  public void synchronizeOpSetWithLocalContactList(
      ProtocolProviderService protocolProvider, final OperationSetMultiUserChat opSet) {
    ChatRoomProviderWrapper chatRoomProvider = findServerWrapperFromProvider(protocolProvider);

    if (chatRoomProvider == null) {
      chatRoomProvider = chatRoomList.addRegisteredChatProvider(protocolProvider);
    }

    if (chatRoomProvider != null) {
      chatRoomProvider.synchronizeProvider();
    }
  }

  /**
   * Returns an iterator to the list of chat room providers.
   *
   * @return an iterator to the list of chat room providers.
   */
  public Iterator<ChatRoomProviderWrapper> getChatRoomProviders() {
    return chatRoomList.getChatRoomProviders();
  }

  /**
   * Removes the given <tt>ChatRoom</tt> from the list of all chat rooms.
   *
   * @param chatRoomWrapper the <tt>ChatRoomWrapper</tt> to remove
   */
  public void removeChatRoom(ChatRoomWrapper chatRoomWrapper) {
    chatRoomList.removeChatRoom(chatRoomWrapper);
  }

  /**
   * Destroys the given <tt>ChatRoom</tt> from the list of all chat rooms.
   *
   * @param chatRoomWrapper the <tt>ChatRoomWrapper</tt> to be destroyed.
   * @param reason the reason for destroying.
   * @param alternateAddress the alternate address.
   */
  public void destroyChatRoom(
      ChatRoomWrapper chatRoomWrapper, String reason, String alternateAddress) {
    if (chatRoomWrapper.getChatRoom().destroy(reason, alternateAddress)) {
      MUCActivator.getUIService().closeChatRoomWindow(chatRoomWrapper);
      chatRoomList.removeChatRoom(chatRoomWrapper);
    } else {
      // if we leave a chat room which is not persistent
      // the room can be destroyed on the server, and error is returned
      // when we try to destroy it not-authorized(401)
      if (!chatRoomWrapper.getChatRoom().isPersistent()
          && !chatRoomWrapper.getChatRoom().isJoined()) {
        chatRoomList.removeChatRoom(chatRoomWrapper);
      }
    }
  }

  /**
   * Adds a ChatRoomProviderWrapperListener to the listener list.
   *
   * @param listener the ChatRoomProviderWrapperListener to be added
   */
  public void addChatRoomProviderWrapperListener(ChatRoomProviderWrapperListener listener) {
    chatRoomList.addChatRoomProviderWrapperListener(listener);
  }

  /**
   * Removes the ChatRoomProviderWrapperListener to the listener list.
   *
   * @param listener the ChatRoomProviderWrapperListener to be removed
   */
  public void removeChatRoomProviderWrapperListener(ChatRoomProviderWrapperListener listener) {
    chatRoomList.removeChatRoomProviderWrapperListener(listener);
  }

  /**
   * Returns the <tt>ChatRoomProviderWrapper</tt> that correspond to the given
   * <tt>ProtocolProviderService</tt>. If the list doesn't contain a corresponding wrapper - returns
   * null.
   *
   * @param protocolProvider the protocol provider that we're looking for
   * @return the <tt>ChatRoomProvider</tt> object corresponding to the given
   *     <tt>ProtocolProviderService</tt>
   */
  public ChatRoomProviderWrapper findServerWrapperFromProvider(
      ProtocolProviderService protocolProvider) {
    return chatRoomList.findServerWrapperFromProvider(protocolProvider);
  }

  /**
   * Returns the <tt>ChatRoomWrapper</tt> that correspond to the given <tt>ChatRoom</tt>. If the
   * list of chat rooms doesn't contain a corresponding wrapper - returns null.
   *
   * @param chatRoom the <tt>ChatRoom</tt> that we're looking for
   * @return the <tt>ChatRoomWrapper</tt> object corresponding to the given <tt>ChatRoom</tt>
   */
  public ChatRoomWrapper findChatRoomWrapperFromChatRoom(ChatRoom chatRoom) {
    return chatRoomList.findChatRoomWrapperFromChatRoom(chatRoom);
  }

  /**
   * Opens a chat window for the chat room.
   *
   * @param room the chat room.
   */
  public void openChatRoom(ChatRoomWrapper room) {
    if (room.getChatRoom() == null) {
      room =
          createChatRoom(
              room.getChatRoomName(),
              room.getParentProvider().getProtocolProvider(),
              new ArrayList<String>(),
              "",
              false,
              false,
              true);

      // leave the chatroom because getChatRoom().isJoined() returns true
      // otherwise
      if (room.getChatRoom().isJoined()) room.getChatRoom().leave();
    }

    if (!room.getChatRoom().isJoined()) {
      String savedNick =
          ConfigurationUtils.getChatRoomProperty(
              room.getParentProvider().getProtocolProvider(), room.getChatRoomID(), "userNickName");
      String subject = null;

      if (savedNick == null) {
        String[] joinOptions =
            ChatRoomJoinOptionsDialog.getJoinOptions(
                room.getParentProvider().getProtocolProvider(),
                room.getChatRoomID(),
                getDefaultNickname(room.getParentProvider().getProtocolProvider()));
        savedNick = joinOptions[0];
        subject = joinOptions[1];
      }

      if (savedNick != null) {
        joinChatRoom(room, savedNick, null, subject);
      } else return;
    }

    MUCActivator.getUIService().openChatRoomWindow(room);
  }

  /**
   * Returns default nickname for chat room based on the given provider.
   *
   * @param pps the given protocol provider service
   * @return default nickname for chat room based on the given provider.
   */
  public String getDefaultNickname(ProtocolProviderService pps) {
    final OperationSetServerStoredAccountInfo accountInfoOpSet =
        pps.getOperationSet(OperationSetServerStoredAccountInfo.class);

    String displayName = "";
    if (accountInfoOpSet != null) {
      displayName = AccountInfoUtils.getDisplayName(accountInfoOpSet);
    }

    if (displayName == null || displayName.length() == 0) {
      displayName = MUCActivator.getGlobalDisplayDetailsService().getGlobalDisplayName();
      if (displayName == null || displayName.length() == 0) {
        displayName = pps.getAccountID().getUserID();
        if (displayName != null) {
          int atIndex = displayName.lastIndexOf("@");
          if (atIndex > 0) displayName = displayName.substring(0, atIndex);
        }
      }
    }

    return displayName;
  }

  /**
   * Returns instance of the <tt>ServerChatRoomContactSourceService</tt> contact source.
   *
   * @return instance of the <tt>ServerChatRoomContactSourceService</tt> contact source.
   */
  public ContactSourceService getServerChatRoomsContactSourceForProvider(
      ChatRoomProviderWrapper pps) {
    return new ServerChatRoomContactSourceService(pps);
  }

  /**
   * Returns <tt>true</tt> if the contact is <tt>ChatRoomSourceContact</tt>
   *
   * @param contact the contact
   * @return <tt>true</tt> if the contact is <tt>ChatRoomSourceContact</tt>
   */
  public boolean isMUCSourceContact(SourceContact contact) {
    return (contact instanceof ChatRoomSourceContact);
  }
}
/**
 * Implements <tt>ContactSourceService</tt> for Google Contacts.
 *
 * @author Sebastien Vincent
 */
public class GoogleContactsSourceService implements ExtendedContactSourceService {
  /** Logger. */
  private static final Logger logger = Logger.getLogger(GoogleContactsSourceService.class);

  /**
   * The <tt>List</tt> of <tt>GoogleContactsQuery</tt> instances which have been started and haven't
   * stopped yet.
   */
  private final List<GoogleContactsQuery> queries = new LinkedList<GoogleContactsQuery>();

  /** Login. */
  private final String login;

  /** Password. */
  private final String password;

  /** The prefix for all google contact phone numbers. */
  private String phoneNumberprefix;

  /** Google Contacts connection. */
  private GoogleContactsConnection cnx = null;

  /** The account settings form. */
  private AccountSettingsForm settings = null;

  /** If the account has been created using GoogleTalk wizard or via external Google Contacts. */
  private boolean googleTalk = false;

  /**
   * Constructor.
   *
   * @param login login
   * @param password password
   */
  public GoogleContactsSourceService(String login, String password) {
    super();
    this.login = login;
    this.password = password;
  }

  /**
   * Constructor.
   *
   * @param cnx connection
   */
  public GoogleContactsSourceService(GoogleContactsConnection cnx) {
    super();
    this.cnx = cnx;
    this.login = cnx.getLogin();
    this.password = cnx.getPassword();
    this.phoneNumberprefix = cnx.getPrefix();
  }

  /**
   * Returns login.
   *
   * @return login
   */
  public String getLogin() {
    return login;
  }

  /**
   * Set whether or not the account has been created via GoogleTalk wizard or external Google
   * contacts.
   *
   * @param googleTalk value to set
   */
  public void setGoogleTalk(boolean googleTalk) {
    this.googleTalk = googleTalk;
  }

  /**
   * Returns whether or not the account has been created via GoogleTalk wizard or via external
   * Google Contacts.
   *
   * @return true if account has been created via GoogleTalk wizard or via external Google Contacts.
   */
  public boolean isGoogleTalk() {
    return googleTalk;
  }

  /**
   * Creates query for the given <tt>searchPattern</tt>.
   *
   * @param queryPattern the pattern to search for
   * @return the created query
   */
  public ContactQuery createContactQuery(Pattern queryPattern) {
    return createContactQuery(queryPattern, GoogleContactsQuery.GOOGLECONTACTS_MAX_RESULTS);
  }

  /**
   * Creates query for the given <tt>searchPattern</tt>.
   *
   * @param queryPattern the pattern to search for
   * @param count maximum number of contact returned
   * @return the created query
   */
  public ContactQuery createContactQuery(Pattern queryPattern, int count) {
    GoogleContactsQuery query = new GoogleContactsQuery(this, queryPattern, count);

    synchronized (queries) {
      queries.add(query);
    }

    return query;
  }

  /**
   * Removes query from the list of queries.
   *
   * @param query the query that will be removed.
   */
  public synchronized void removeQuery(ContactQuery query) {
    if (queries.remove(query)) queries.notify();
  }

  /**
   * Returns the Google Contacts connection.
   *
   * @return Google Contacts connection
   */
  public GoogleContactsConnectionImpl getConnection() {
    int s = login.indexOf('@');
    boolean isGoogleAppsOrGmail = false;

    if (s == -1) {
      return null;
    }

    String domain = login.substring((s + 1));

    try {
      SRVRecord srvRecords[] = NetworkUtils.getSRVRecords("xmpp-client", "tcp", domain);

      if (srvRecords != null) {
        // To detect that account is a google ones, we try following:
        // - lookup in SRV and see if it is google.com;
        // - if the account has been created with GoogleTalk form;
        // - if it is an "external" google contact.

        // SRV checks
        for (SRVRecord srv : srvRecords) {
          if (srv.getTarget().endsWith("google.com") || srv.getTarget().endsWith("google.com.")) {
            isGoogleAppsOrGmail = true;
            break;
          }
        }
      }

      // GoogleTalk based account or external Google Contacts ?
      if (!isGoogleAppsOrGmail) {
        isGoogleAppsOrGmail = googleTalk;
      }

      if (isGoogleAppsOrGmail) {
        if (cnx == null) {
          cnx = new GoogleContactsConnectionImpl(login, password);

          if (cnx.connect()
              == GoogleContactsConnection.ConnectionStatus.ERROR_INVALID_CREDENTIALS) {
            synchronized (this) {
              if (settings != null) {
                cnx = null;
                return null;
              } else {
                settings = new AccountSettingsForm();
              }
            }
            settings.setModal(true);
            settings.loadData(cnx);
            int ret = settings.showDialog();

            if (ret == 1) {
              cnx = settings.getConnection();
              GoogleContactsActivator.getGoogleContactsService().saveConfig(cnx);
            } else {
              cnx = null;
            }
          }
        } else if (cnx.connect()
            == GoogleContactsConnection.ConnectionStatus.ERROR_INVALID_CREDENTIALS) {
          synchronized (this) {
            if (settings != null) {
              cnx = null;
              return null;
            } else {
              settings = new AccountSettingsForm();
            }
          }
          settings.setModal(true);
          settings.loadData(cnx);
          int ret = settings.showDialog();

          if (ret == 1) {
            cnx = settings.getConnection();
            GoogleContactsActivator.getGoogleContactsService().saveConfig(cnx);
          } else {
            cnx = null;
          }
        }
      } else {
        cnx = null;
      }
    } catch (Exception e) {
      logger.info("GoogleContacts connection error", e);
      return null;
    }

    return (GoogleContactsConnectionImpl) cnx;
  }

  /**
   * Returns a user-friendly string that identifies this contact source.
   *
   * @return the display name of this contact source
   */
  public String getDisplayName() {
    return login;
  }

  /**
   * Returns SEARCH_TYPE to indicate that this contact source
   *
   * @return the identifier of this contact source
   */
  public int getType() {
    return SEARCH_TYPE;
  }

  /**
   * Queries this search source for the given <tt>queryString</tt>.
   *
   * @param query the string to search for
   * @return the created query
   */
  public ContactQuery createContactQuery(String query) {
    return createContactQuery(query, GoogleContactsQuery.GOOGLECONTACTS_MAX_RESULTS);
  }

  /**
   * Creates query for the given <tt>queryString</tt>.
   *
   * @param query the string to search for
   * @param contactCount the maximum count of result contacts
   * @return the created query
   */
  public ContactQuery createContactQuery(String query, int contactCount) {
    Pattern pattern = null;
    try {
      pattern = Pattern.compile(query);
    } catch (PatternSyntaxException pse) {
      pattern = Pattern.compile(Pattern.quote(query));
    }

    if (pattern != null) {
      return createContactQuery(pattern, contactCount);
    }
    return null;
  }

  /**
   * Stops this <tt>ContactSourceService</tt> implementation and prepares it for garbage collection.
   *
   * @see AsyncContactSourceService#stop()
   */
  public void stop() {
    boolean interrupted = false;

    synchronized (queries) {
      while (!queries.isEmpty()) {
        queries.get(0).cancel();
        try {
          queries.wait();
        } catch (InterruptedException iex) {
          interrupted = true;
        }
      }
    }
    if (interrupted) Thread.currentThread().interrupt();
  }

  /**
   * Notifies this <tt>GoogleContactsSourceService</tt> that a specific <tt>GoogleContactsQuery</tt>
   * has stopped.
   *
   * @param query the <tt>GoogleContactsQuery</tt> which has stopped
   */
  void stopped(GoogleContactsQuery query) {
    synchronized (queries) {
      if (queries.remove(query)) queries.notify();
    }
  }

  /**
   * Returns the phoneNumber prefix for all phone numbers.
   *
   * @return the phoneNumber prefix for all phone numbers
   */
  public String getPhoneNumberPrefix() {
    return phoneNumberprefix;
  }

  /**
   * Sets the phone number prefix.
   *
   * @param phoneNumberprefix the phone number prefix to set
   */
  public void setPhoneNumberPrefix(String phoneNumberprefix) {
    this.phoneNumberprefix = phoneNumberprefix;
  }

  /**
   * Returns the index of the contact source in the result list.
   *
   * @return the index of the contact source in the result list
   */
  public int getIndex() {
    return -1;
  }
}
示例#3
0
/**
 * The <tt>PresenceFilter</tt> is used to filter offline contacts from the contact list.
 *
 * @author Yana Stamcheva
 */
public class PresenceFilter implements ContactListFilter {
  /**
   * The <tt>Logger</tt> used by the <tt>PresenceFilter</tt> class and its instances to print
   * debugging information.
   */
  private static final Logger logger = Logger.getLogger(PresenceFilter.class);

  /** Indicates if this presence filter shows or hides the offline contacts. */
  private boolean isShowOffline;

  /**
   * The initial result count below which we insert all filter results directly to the contact list
   * without firing events.
   */
  private static final int INITIAL_CONTACT_COUNT = 30;

  /**
   * Preferences for the external contact sources. Lists the type of contact contact sources that
   * will be displayed in the filter and the order of the contact sources.
   */
  private static Map<Integer, Integer> contactSourcePreferences = new HashMap<Integer, Integer>();

  /** Creates an instance of <tt>PresenceFilter</tt>. */
  public PresenceFilter() {
    isShowOffline = ConfigurationUtils.isShowOffline();
    initContactSourcePreferences();
  }

  /**
   * Initializes the contact source preferences. The preferences are for the visibility of the
   * contact source and their order.
   */
  private void initContactSourcePreferences() {
    // This entry will be used to set the index for chat room contact sources
    // The index is used to order the contact sources in the contact list.
    // The chat room sources will be ordered before the meta contact list.
    contactSourcePreferences.put(ContactSourceService.CHAT_ROOM_TYPE, 0);
  }

  /**
   * Applies this filter. This filter is applied over the <tt>MetaContactListService</tt>.
   *
   * @param filterQuery the query which keeps track of the filtering results
   */
  public void applyFilter(FilterQuery filterQuery) {
    // Create the query that will track filtering.
    MetaContactQuery query = new MetaContactQuery();

    // Add this query to the filterQuery.
    filterQuery.addContactQuery(query);

    for (int cssType : contactSourcePreferences.keySet()) {
      Iterator<UIContactSource> filterSources =
          GuiActivator.getContactList().getContactSources(cssType).iterator();

      while (filterSources.hasNext()) {
        UIContactSource filterSource = filterSources.next();

        Integer prefValue = contactSourcePreferences.get(cssType);
        // We are setting the index from contactSourcePreferences map to
        // the contact source. This index is set to reorder the sources
        // in the contact list.
        if (prefValue != null) filterSource.setContactSourceIndex(prefValue);

        ContactSourceService sourceService = filterSource.getContactSourceService();

        ContactQuery contactQuery = sourceService.createContactQuery(null);

        // Add this query to the filterQuery.
        filterQuery.addContactQuery(contactQuery);

        contactQuery.addContactQueryListener(GuiActivator.getContactList());

        contactQuery.start();
      }
    }

    // Closes this filter to indicate that we finished adding queries to it.
    filterQuery.close();

    query.addContactQueryListener(GuiActivator.getContactList());
    int resultCount = 0;

    addMatching(GuiActivator.getContactListService().getRoot(), query, resultCount);

    query.fireQueryEvent(
        query.isCanceled()
            ? MetaContactQueryStatusEvent.QUERY_CANCELED
            : MetaContactQueryStatusEvent.QUERY_COMPLETED);
  }

  /**
   * Indicates if the given <tt>uiContact</tt> is matching this filter.
   *
   * @param uiContact the <tt>UIContact</tt> to check
   * @return <tt>true</tt> if the given <tt>uiContact</tt> is matching this filter, otherwise
   *     returns <tt>false</tt>
   */
  public boolean isMatching(UIContact uiContact) {
    Object descriptor = uiContact.getDescriptor();

    if (descriptor instanceof MetaContact) return isMatching((MetaContact) descriptor);
    else if (descriptor instanceof SourceContact) return isMatching((SourceContact) descriptor);
    else return false;
  }

  /**
   * Indicates if the given <tt>uiGroup</tt> is matching this filter.
   *
   * @param uiGroup the <tt>UIGroup</tt> to check
   * @return <tt>true</tt> if the given <tt>uiGroup</tt> is matching this filter, otherwise returns
   *     <tt>false</tt>
   */
  public boolean isMatching(UIGroup uiGroup) {
    Object descriptor = uiGroup.getDescriptor();

    if (descriptor instanceof MetaContactGroup) return isMatching((MetaContactGroup) descriptor);
    else return false;
  }

  /**
   * Sets the show offline property.
   *
   * @param isShowOffline indicates if offline contacts are shown
   */
  public void setShowOffline(boolean isShowOffline) {
    this.isShowOffline = isShowOffline;

    ConfigurationUtils.setShowOffline(isShowOffline);
  }

  /**
   * Returns <tt>true</tt> if offline contacts are shown, otherwise returns <tt>false</tt>.
   *
   * @return <tt>true</tt> if offline contacts are shown, otherwise returns <tt>false</tt>
   */
  public boolean isShowOffline() {
    return isShowOffline;
  }

  /**
   * Returns <tt>true</tt> if offline contacts are shown or if the given <tt>MetaContact</tt> is
   * online, otherwise returns false.
   *
   * @param metaContact the <tt>MetaContact</tt> to check
   * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this filter
   */
  public boolean isMatching(MetaContact metaContact) {
    return isShowOffline || isContactOnline(metaContact);
  }

  /**
   * Returns <tt>true</tt> if offline contacts are shown or if the given <tt>MetaContact</tt> is
   * online, otherwise returns false.
   *
   * @param metaContact the <tt>MetaContact</tt> to check
   * @return <tt>true</tt> if the given <tt>MetaContact</tt> is matching this filter
   */
  public boolean isMatching(SourceContact contact) {
    return isShowOffline
        || contact.getPresenceStatus().isOnline()
        || GuiActivator.getMUCService().isMUCSourceContact(contact);
  }

  /**
   * Returns <tt>true</tt> if offline contacts are shown or if the given <tt>MetaContactGroup</tt>
   * contains online contacts.
   *
   * @param metaGroup the <tt>MetaContactGroup</tt> to check
   * @return <tt>true</tt> if the given <tt>MetaContactGroup</tt> is matching this filter
   */
  private boolean isMatching(MetaContactGroup metaGroup) {
    return isShowOffline
        || (metaGroup.countOnlineChildContacts() > 0)
        || MetaContactListSource.isNewGroup(metaGroup);
  }

  /**
   * Returns <tt>true</tt> if the given meta contact is online, <tt>false</tt> otherwise.
   *
   * @param contact the meta contact
   * @return <tt>true</tt> if the given meta contact is online, <tt>false</tt> otherwise
   */
  private boolean isContactOnline(MetaContact contact) {
    // If for some reason the default contact is null we return false.
    Contact defaultContact = contact.getDefaultContact();
    if (defaultContact == null) return false;

    // Lays on the fact that the default contact is the most connected.
    return defaultContact.getPresenceStatus().getStatus() >= PresenceStatus.ONLINE_THRESHOLD;
  }

  /**
   * Adds all contacts contained in the given <tt>MetaContactGroup</tt> matching the current filter
   * and not contained in the contact list.
   *
   * @param metaGroup the <tt>MetaContactGroup</tt>, which matching contacts to add
   * @param query the <tt>MetaContactQuery</tt> that notifies interested listeners of the results of
   *     this matching
   * @param resultCount the initial result count we would insert directly to the contact list
   *     without firing events
   */
  private void addMatching(MetaContactGroup metaGroup, MetaContactQuery query, int resultCount) {
    Iterator<MetaContact> childContacts = metaGroup.getChildContacts();

    while (childContacts.hasNext() && !query.isCanceled()) {
      MetaContact metaContact = childContacts.next();

      if (isMatching(metaContact)) {

        resultCount++;
        if (resultCount <= INITIAL_CONTACT_COUNT) {
          UIGroup uiGroup = null;

          if (!MetaContactListSource.isRootGroup(metaGroup)) {
            synchronized (metaGroup) {
              uiGroup = MetaContactListSource.getUIGroup(metaGroup);
              if (uiGroup == null) uiGroup = MetaContactListSource.createUIGroup(metaGroup);
            }
          }

          if (logger.isDebugEnabled())
            logger.debug("Presence filter contact added: " + metaContact.getDisplayName());

          UIContactImpl newUIContact;
          synchronized (metaContact) {
            newUIContact = MetaContactListSource.getUIContact(metaContact);

            if (newUIContact == null) {
              newUIContact = MetaContactListSource.createUIContact(metaContact);
            }
          }

          GuiActivator.getContactList().addContact(newUIContact, uiGroup, true, true);

          query.setInitialResultCount(resultCount);
        } else {
          query.fireQueryEvent(metaContact);
        }
      }
    }

    // If in the meantime the filtering has been stopped we return here.
    if (query.isCanceled()) return;

    Iterator<MetaContactGroup> subgroups = metaGroup.getSubgroups();
    while (subgroups.hasNext() && !query.isCanceled()) {
      MetaContactGroup subgroup = subgroups.next();

      if (isMatching(subgroup)) {
        UIGroup uiGroup;
        synchronized (subgroup) {
          uiGroup = MetaContactListSource.getUIGroup(subgroup);

          if (uiGroup == null) uiGroup = MetaContactListSource.createUIGroup(subgroup);
        }

        GuiActivator.getContactList().addGroup(uiGroup, true);

        addMatching(subgroup, query, resultCount);
      }
    }
  }
}
/**
 * The source contact service. The will show most recent messages.
 *
 * @author Damian Minkov
 */
public class MessageSourceService extends MetaContactListAdapter
    implements ContactSourceService,
        ContactPresenceStatusListener,
        ContactCapabilitiesListener,
        ProviderPresenceStatusListener,
        SubscriptionListener,
        LocalUserChatRoomPresenceListener,
        MessageListener,
        ChatRoomMessageListener,
        AdHocChatRoomMessageListener {
  /** The logger for this class. */
  private static Logger logger = Logger.getLogger(MessageSourceService.class);

  /** The display name of this contact source. */
  private final String MESSAGE_HISTORY_NAME;

  /** The type of the source service, the place to be shown in the ui. */
  private int sourceServiceType = CONTACT_LIST_TYPE;

  /**
   * Whether to show recent messages in history or in contactlist. By default we show it in
   * contactlist.
   */
  private static final String IN_HISTORY_PROPERTY =
      "net.java.sip.communicator.impl.msghistory.contactsrc.IN_HISTORY";

  /** Property to control number of recent messages. */
  private static final String NUMBER_OF_RECENT_MSGS_PROP =
      "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_NUMBER";

  /** Property to control version of recent messages. */
  private static final String VER_OF_RECENT_MSGS_PROP =
      "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_VER";

  /** Property to control messages type. Can query for message sub type. */
  private static final String IS_MESSAGE_SUBTYPE_SMS_PROP =
      "net.java.sip.communicator.impl.msghistory.contactsrc.IS_SMS_ENABLED";

  /**
   * The number of recent messages to store in the history, but will retrieve just
   * <tt>numberOfMessages</tt>
   */
  private static final int NUMBER_OF_MSGS_IN_HISTORY = 100;

  /** Number of messages to show. */
  private int numberOfMessages = 10;

  /** The structure to save recent messages list. */
  private static final String[] STRUCTURE_NAMES =
      new String[] {"provider", "contact", "timestamp", "ver"};

  /** The current version of recent messages. When changed the recent messages are recreated. */
  private static String RECENT_MSGS_VER = "2";

  /** The structure. */
  private static final HistoryRecordStructure recordStructure =
      new HistoryRecordStructure(STRUCTURE_NAMES);

  /** Recent messages history ID. */
  private static final HistoryID historyID =
      HistoryID.createFromRawID(new String[] {"recent_messages"});

  /** The cache for recent messages. */
  private History history = null;

  /** List of recent messages. */
  private final List<ComparableEvtObj> recentMessages = new LinkedList<ComparableEvtObj>();

  /** Date of the oldest shown message. */
  private Date oldestRecentMessage = null;

  /** The last query created. */
  private MessageSourceContactQuery recentQuery = null;

  /** The message subtype if any. */
  private boolean isSMSEnabled = false;

  /** Message history service that has created us. */
  private MessageHistoryServiceImpl messageHistoryService;

  /** Constructs MessageSourceService. */
  MessageSourceService(MessageHistoryServiceImpl messageHistoryService) {
    this.messageHistoryService = messageHistoryService;

    ConfigurationService conf = MessageHistoryActivator.getConfigurationService();

    if (conf.getBoolean(IN_HISTORY_PROPERTY, false)) {
      sourceServiceType = HISTORY_TYPE;
    }

    MESSAGE_HISTORY_NAME =
        MessageHistoryActivator.getResources().getI18NString("service.gui.RECENT_MESSAGES");

    numberOfMessages = conf.getInt(NUMBER_OF_RECENT_MSGS_PROP, numberOfMessages);

    isSMSEnabled = conf.getBoolean(IS_MESSAGE_SUBTYPE_SMS_PROP, isSMSEnabled);

    RECENT_MSGS_VER = conf.getString(VER_OF_RECENT_MSGS_PROP, RECENT_MSGS_VER);

    MessageSourceContactPresenceStatus.MSG_SRC_CONTACT_ONLINE.setStatusIcon(
        MessageHistoryActivator.getResources()
            .getImageInBytes("service.gui.icons.SMS_STATUS_ICON"));
  }

  /**
   * Returns the display name of this contact source.
   *
   * @return the display name of this contact source
   */
  @Override
  public String getDisplayName() {
    return MESSAGE_HISTORY_NAME;
  }

  /**
   * Returns default type to indicate that this contact source can be queried by default filters.
   *
   * @return the type of this contact source
   */
  @Override
  public int getType() {
    return sourceServiceType;
  }

  /**
   * Returns the index of the contact source in the result list.
   *
   * @return the index of the contact source in the result list
   */
  @Override
  public int getIndex() {
    return 0;
  }

  /**
   * Creates query for the given <tt>searchString</tt>.
   *
   * @param queryString the string to search for
   * @return the created query
   */
  @Override
  public ContactQuery createContactQuery(String queryString) {
    recentQuery = (MessageSourceContactQuery) createContactQuery(queryString, numberOfMessages);

    return recentQuery;
  }

  /**
   * Updates the contact sources in the recent query if any. Done here in order to sync with
   * recentMessages instance, and to check for already existing instances of contact sources.
   * Normally called from the query.
   */
  public void updateRecentMessages() {
    if (recentQuery == null) return;

    synchronized (recentMessages) {
      List<SourceContact> currentContactsInQuery = recentQuery.getQueryResults();

      for (ComparableEvtObj evtObj : recentMessages) {
        // the contains will use the correct equals method of
        // the object evtObj
        if (!currentContactsInQuery.contains(evtObj)) {
          MessageSourceContact newSourceContact =
              new MessageSourceContact(evtObj.getEventObject(), MessageSourceService.this);
          newSourceContact.initDetails(evtObj.getEventObject());

          recentQuery.addQueryResult(newSourceContact);
        }
      }
    }
  }

  /**
   * Searches for entries in cached recent messages in history.
   *
   * @param provider the provider which contact messages we will search
   * @param isStatusChanged is the search because of status changed
   * @return entries in cached recent messages in history.
   */
  private List<ComparableEvtObj> getCachedRecentMessages(
      ProtocolProviderService provider, boolean isStatusChanged) {
    String providerID = provider.getAccountID().getAccountUniqueID();
    List<String> recentMessagesContactIDs =
        getRecentContactIDs(
            providerID, recentMessages.size() < numberOfMessages ? null : oldestRecentMessage);

    List<ComparableEvtObj> cachedRecentMessages = new ArrayList<ComparableEvtObj>();

    for (String contactID : recentMessagesContactIDs) {
      Collection<EventObject> res =
          messageHistoryService.findRecentMessagesPerContact(
              numberOfMessages, providerID, contactID, isSMSEnabled);

      processEventObjects(res, cachedRecentMessages, isStatusChanged);
    }

    return cachedRecentMessages;
  }

  /**
   * Process list of event objects. Checks whether message source contact already exist for this
   * event object, if yes just update it with the new values (not sure whether we should do this, as
   * it may bring old messages) and if status of provider is changed, init its details, updates its
   * capabilities. It still adds the found messages source contact to the list of the new contacts,
   * as later we will detect this and fire update event. If nothing found a new contact is created.
   *
   * @param res list of event
   * @param cachedRecentMessages list of newly created source contacts or already existed but
   *     updated with corresponding event object
   * @param isStatusChanged whether provider status changed and we are processing
   */
  private void processEventObjects(
      Collection<EventObject> res,
      List<ComparableEvtObj> cachedRecentMessages,
      boolean isStatusChanged) {
    for (EventObject obj : res) {
      ComparableEvtObj oldMsg = findRecentMessage(obj, recentMessages);

      if (oldMsg != null) {
        oldMsg.update(obj); // update

        if (isStatusChanged && recentQuery != null) recentQuery.updateCapabilities(oldMsg, obj);

        // we still add it to cachedRecentMessages
        // later we will find it is duplicate and will fire
        // update event
        if (!cachedRecentMessages.contains(oldMsg)) cachedRecentMessages.add(oldMsg);

        continue;
      }

      oldMsg = findRecentMessage(obj, cachedRecentMessages);

      if (oldMsg == null) {
        oldMsg = new ComparableEvtObj(obj);

        if (isStatusChanged && recentQuery != null) recentQuery.updateCapabilities(oldMsg, obj);

        cachedRecentMessages.add(oldMsg);
      }
    }
  }

  /**
   * Access for source contacts impl.
   *
   * @return
   */
  boolean isSMSEnabled() {
    return isSMSEnabled;
  }

  /**
   * Add the ComparableEvtObj, newly added will fire new, for existing fire update and when trimming
   * the list to desired length fire remove for those that were removed
   *
   * @param contactsToAdd
   */
  private void addNewRecentMessages(List<ComparableEvtObj> contactsToAdd) {
    // now find object to fire new, and object to fire remove
    // let us find duplicates and fire update
    List<ComparableEvtObj> duplicates = new ArrayList<ComparableEvtObj>();
    for (ComparableEvtObj msgToAdd : contactsToAdd) {
      if (recentMessages.contains(msgToAdd)) {
        duplicates.add(msgToAdd);

        // save update
        updateRecentMessageToHistory(msgToAdd);
      }
    }
    recentMessages.removeAll(duplicates);

    // now contacts to add has no duplicates, add them all
    boolean changed = recentMessages.addAll(contactsToAdd);

    if (changed) {
      Collections.sort(recentMessages);

      if (recentQuery != null) {
        for (ComparableEvtObj obj : duplicates)
          recentQuery.updateContact(obj, obj.getEventObject());
      }
    }

    if (!recentMessages.isEmpty())
      oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp();

    // trim
    List<ComparableEvtObj> removedItems = null;
    if (recentMessages.size() > numberOfMessages) {
      removedItems =
          new ArrayList<ComparableEvtObj>(
              recentMessages.subList(numberOfMessages, recentMessages.size()));

      recentMessages.removeAll(removedItems);
    }

    if (recentQuery != null) {
      // now fire, removed for all that were in the list
      // and now are removed after trim
      if (removedItems != null) {
        for (ComparableEvtObj msc : removedItems) {
          if (!contactsToAdd.contains(msc)) recentQuery.fireContactRemoved(msc);
        }
      }

      // fire new for all that were added, and not removed after trim
      for (ComparableEvtObj msc : contactsToAdd) {
        if ((removedItems == null || !removedItems.contains(msc)) && !duplicates.contains(msc)) {
          MessageSourceContact newSourceContact =
              new MessageSourceContact(msc.getEventObject(), MessageSourceService.this);
          newSourceContact.initDetails(msc.getEventObject());

          recentQuery.addQueryResult(newSourceContact);
        }
      }

      // if recent messages were changed, indexes have change lets
      // fire event for the last element which will reorder the whole
      // group if needed.
      if (changed) recentQuery.fireContactChanged(recentMessages.get(recentMessages.size() - 1));
    }
  }

  /**
   * When a provider is added, do not block and start executing in new thread.
   *
   * @param provider ProtocolProviderService
   */
  void handleProviderAdded(final ProtocolProviderService provider, final boolean isStatusChanged) {
    new Thread(
            new Runnable() {
              @Override
              public void run() {
                handleProviderAddedInSeparateThread(provider, isStatusChanged);
              }
            })
        .start();
  }

  /**
   * When a provider is added. As searching can be slow especially when handling special type of
   * messages (with subType) this need to be run in new Thread.
   *
   * @param provider ProtocolProviderService
   */
  private void handleProviderAddedInSeparateThread(
      ProtocolProviderService provider, boolean isStatusChanged) {
    // lets check if we have cached recent messages for this provider, and
    // fire events if found and are newer

    synchronized (recentMessages) {
      List<ComparableEvtObj> cachedRecentMessages =
          getCachedRecentMessages(provider, isStatusChanged);

      if (cachedRecentMessages.isEmpty()) {
        // maybe there is no cached history for this
        // let's check
        // load it not from cache, but do a local search
        Collection<EventObject> res =
            messageHistoryService.findRecentMessagesPerContact(
                numberOfMessages, provider.getAccountID().getAccountUniqueID(), null, isSMSEnabled);

        List<ComparableEvtObj> newMsc = new ArrayList<ComparableEvtObj>();

        processEventObjects(res, newMsc, isStatusChanged);

        addNewRecentMessages(newMsc);

        for (ComparableEvtObj msc : newMsc) {
          saveRecentMessageToHistory(msc);
        }
      } else addNewRecentMessages(cachedRecentMessages);
    }
  }

  /**
   * Tries to match the event object to already existing ComparableEvtObj in the supplied list.
   *
   * @param obj the object that we will try to match.
   * @param list the list we will search in.
   * @return the found ComparableEvtObj
   */
  private static ComparableEvtObj findRecentMessage(EventObject obj, List<ComparableEvtObj> list) {
    Contact contact = null;
    ChatRoom chatRoom = null;

    if (obj instanceof MessageDeliveredEvent) {
      contact = ((MessageDeliveredEvent) obj).getDestinationContact();
    } else if (obj instanceof MessageReceivedEvent) {
      contact = ((MessageReceivedEvent) obj).getSourceContact();
    } else if (obj instanceof ChatRoomMessageDeliveredEvent) {
      chatRoom = ((ChatRoomMessageDeliveredEvent) obj).getSourceChatRoom();
    } else if (obj instanceof ChatRoomMessageReceivedEvent) {
      chatRoom = ((ChatRoomMessageReceivedEvent) obj).getSourceChatRoom();
    }

    for (ComparableEvtObj evt : list) {
      if ((contact != null && contact.equals(evt.getContact()))
          || (chatRoom != null && chatRoom.equals(evt.getRoom()))) return evt;
    }

    return null;
  }

  /**
   * A provider has been removed.
   *
   * @param provider the ProtocolProviderService that has been unregistered.
   */
  void handleProviderRemoved(ProtocolProviderService provider) {
    // lets remove the recent messages for this provider, and update
    // with recent messages for the available providers
    synchronized (recentMessages) {
      if (provider != null) {
        List<ComparableEvtObj> removedItems = new ArrayList<ComparableEvtObj>();
        for (ComparableEvtObj msc : recentMessages) {
          if (msc.getProtocolProviderService().equals(provider)) removedItems.add(msc);
        }

        recentMessages.removeAll(removedItems);
        if (!recentMessages.isEmpty())
          oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp();
        else oldestRecentMessage = null;

        if (recentQuery != null) {
          for (ComparableEvtObj msc : removedItems) {
            recentQuery.fireContactRemoved(msc);
          }
        }
      }

      // handleProviderRemoved can be invoked due to stopped
      // history service, if this is the case we do not want to
      // update messages
      if (!this.messageHistoryService.isHistoryLoggingEnabled()) return;

      // lets do the same as we enable provider
      // for all registered providers and finally fire events
      List<ComparableEvtObj> contactsToAdd = new ArrayList<ComparableEvtObj>();
      for (ProtocolProviderService pps : messageHistoryService.getCurrentlyAvailableProviders()) {
        contactsToAdd.addAll(getCachedRecentMessages(pps, true));
      }

      addNewRecentMessages(contactsToAdd);
    }
  }

  /**
   * Searches for contact ids in history of recent messages.
   *
   * @param provider
   * @param after
   * @return
   */
  List<String> getRecentContactIDs(String provider, Date after) {
    List<String> res = new ArrayList<String>();

    try {
      History history = getHistory();

      if (history != null) {
        Iterator<HistoryRecord> recs = history.getReader().findLast(NUMBER_OF_MSGS_IN_HISTORY);
        SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT);

        while (recs.hasNext()) {
          HistoryRecord hr = recs.next();

          String contact = null;
          String recordProvider = null;
          Date timestamp = null;

          for (int i = 0; i < hr.getPropertyNames().length; i++) {
            String propName = hr.getPropertyNames()[i];

            if (propName.equals(STRUCTURE_NAMES[0])) recordProvider = hr.getPropertyValues()[i];
            else if (propName.equals(STRUCTURE_NAMES[1])) contact = hr.getPropertyValues()[i];
            else if (propName.equals(STRUCTURE_NAMES[2])) {
              try {
                timestamp = sdf.parse(hr.getPropertyValues()[i]);
              } catch (ParseException e) {
                timestamp = new Date(Long.parseLong(hr.getPropertyValues()[i]));
              }
            }
          }

          if (recordProvider == null || contact == null) continue;

          if (after != null && timestamp != null && timestamp.before(after)) continue;

          if (recordProvider.equals(provider)) res.add(contact);
        }
      }
    } catch (IOException ex) {
      logger.error("cannot create recent_messages history", ex);
    }

    return res;
  }

  /**
   * Returns the cached recent messages history.
   *
   * @return
   * @throws IOException
   */
  private History getHistory() throws IOException {
    synchronized (historyID) {
      HistoryService historyService =
          MessageHistoryActivator.getMessageHistoryService().getHistoryService();

      if (history == null) {
        history = historyService.createHistory(historyID, recordStructure);

        // lets check the version if not our version, re-create
        // history (delete it)
        HistoryReader reader = history.getReader();
        boolean delete = false;
        QueryResultSet<HistoryRecord> res = reader.findLast(1);
        if (res != null && res.hasNext()) {
          HistoryRecord hr = res.next();
          if (hr.getPropertyValues().length >= 4) {
            if (!hr.getPropertyValues()[3].equals(RECENT_MSGS_VER)) delete = true;
          } else delete = true;
        }

        if (delete) {
          // delete it
          try {
            historyService.purgeLocallyStoredHistory(historyID);

            history = historyService.createHistory(historyID, recordStructure);
          } catch (IOException ex) {
            logger.error("Cannot delete recent_messages history", ex);
          }
        }
      }

      return history;
    }
  }

  /**
   * Returns the index of the source contact, in the list of recent messages.
   *
   * @param messageSourceContact
   * @return
   */
  int getIndex(MessageSourceContact messageSourceContact) {
    synchronized (recentMessages) {
      for (int i = 0; i < recentMessages.size(); i++)
        if (recentMessages.get(i).equals(messageSourceContact)) return i;

      return -1;
    }
  }

  /**
   * Creates query for the given <tt>searchString</tt>.
   *
   * @param queryString the string to search for
   * @param contactCount the maximum count of result contacts
   * @return the created query
   */
  @Override
  public ContactQuery createContactQuery(String queryString, int contactCount) {
    if (!StringUtils.isNullOrEmpty(queryString)) return null;

    recentQuery = new MessageSourceContactQuery(MessageSourceService.this);

    return recentQuery;
  }

  /**
   * Updates contact source contacts with status.
   *
   * @param evt the ContactPresenceStatusChangeEvent describing the status
   */
  @Override
  public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) {
    if (recentQuery == null) return;

    synchronized (recentMessages) {
      for (ComparableEvtObj msg : recentMessages) {
        if (msg.getContact() != null && msg.getContact().equals(evt.getSourceContact())) {
          recentQuery.updateContactStatus(msg, evt.getNewStatus());
        }
      }
    }
  }

  @Override
  public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) {
    if (!evt.getNewStatus().isOnline() || evt.getOldStatus().isOnline()) return;

    handleProviderAdded(evt.getProvider(), true);
  }

  @Override
  public void providerStatusMessageChanged(PropertyChangeEvent evt) {}

  @Override
  public void localUserPresenceChanged(LocalUserChatRoomPresenceChangeEvent evt) {
    if (recentQuery == null) return;

    ComparableEvtObj srcContact = null;

    synchronized (recentMessages) {
      for (ComparableEvtObj msg : recentMessages) {
        if (msg.getRoom() != null && msg.getRoom().equals(evt.getChatRoom())) {
          srcContact = msg;
          break;
        }
      }
    }

    if (srcContact == null) return;

    String eventType = evt.getEventType();

    if (LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED.equals(eventType)) {
      recentQuery.updateContactStatus(srcContact, ChatRoomPresenceStatus.CHAT_ROOM_ONLINE);
    } else if ((LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_LEFT.equals(eventType)
        || LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_KICKED.equals(eventType)
        || LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_DROPPED.equals(eventType))) {
      recentQuery.updateContactStatus(srcContact, ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE);
    }
  }

  /**
   * Handles new events.
   *
   * @param obj the event object
   * @param provider the provider
   * @param id the id of the source of the event
   */
  private void handle(EventObject obj, ProtocolProviderService provider, String id) {
    // check if provider - contact exist update message content
    synchronized (recentMessages) {
      ComparableEvtObj existingMsc = null;
      for (ComparableEvtObj msc : recentMessages) {
        if (msc.getProtocolProviderService().equals(provider)
            && msc.getContactAddress().equals(id)) {
          // update
          msc.update(obj);
          updateRecentMessageToHistory(msc);

          existingMsc = msc;
        }
      }

      if (existingMsc != null) {
        Collections.sort(recentMessages);
        oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp();

        if (recentQuery != null) {
          recentQuery.updateContact(existingMsc, existingMsc.getEventObject());
          recentQuery.fireContactChanged(existingMsc);
        }

        return;
      }

      // if missing create source contact
      // and update recent messages, trim and sort
      MessageSourceContact newSourceContact =
          new MessageSourceContact(obj, MessageSourceService.this);
      newSourceContact.initDetails(obj);
      // we have already checked for duplicate
      ComparableEvtObj newMsg = new ComparableEvtObj(obj);
      recentMessages.add(newMsg);

      Collections.sort(recentMessages);
      oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp();

      // trim
      List<ComparableEvtObj> removedItems = null;
      if (recentMessages.size() > numberOfMessages) {
        removedItems =
            new ArrayList<ComparableEvtObj>(
                recentMessages.subList(numberOfMessages, recentMessages.size()));

        recentMessages.removeAll(removedItems);
      }

      // save
      saveRecentMessageToHistory(newMsg);

      // no query nothing to fire
      if (recentQuery == null) return;

      // now fire
      if (removedItems != null) {
        for (ComparableEvtObj msc : removedItems) {
          recentQuery.fireContactRemoved(msc);
        }
      }

      recentQuery.addQueryResult(newSourceContact);
    }
  }

  /** Adds recent message in history. */
  private void saveRecentMessageToHistory(ComparableEvtObj msc) {
    synchronized (historyID) {
      // and create it
      try {
        History history = getHistory();
        HistoryWriter writer = history.getWriter();

        SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT);

        writer.addRecord(
            new String[] {
              msc.getProtocolProviderService().getAccountID().getAccountUniqueID(),
              msc.getContactAddress(),
              sdf.format(msc.getTimestamp()),
              RECENT_MSGS_VER
            },
            NUMBER_OF_MSGS_IN_HISTORY);
      } catch (IOException ex) {
        logger.error("cannot create recent_messages history", ex);
        return;
      }
    }
  }

  /** Updates recent message in history. */
  private void updateRecentMessageToHistory(final ComparableEvtObj msg) {
    synchronized (historyID) {
      // and create it
      try {
        History history = getHistory();

        HistoryWriter writer = history.getWriter();

        writer.updateRecord(
            new HistoryWriter.HistoryRecordUpdater() {
              HistoryRecord hr;

              @Override
              public void setHistoryRecord(HistoryRecord historyRecord) {
                this.hr = historyRecord;
              }

              @Override
              public boolean isMatching() {
                boolean providerFound = false;
                boolean contactFound = false;
                for (int i = 0; i < hr.getPropertyNames().length; i++) {
                  String propName = hr.getPropertyNames()[i];

                  if (propName.equals(STRUCTURE_NAMES[0])) {
                    if (msg.getProtocolProviderService()
                        .getAccountID()
                        .getAccountUniqueID()
                        .equals(hr.getPropertyValues()[i])) {
                      providerFound = true;
                    }
                  } else if (propName.equals(STRUCTURE_NAMES[1])) {
                    if (msg.getContactAddress().equals(hr.getPropertyValues()[i])) {
                      contactFound = true;
                    }
                  }
                }

                return contactFound && providerFound;
              }

              @Override
              public Map<String, String> getUpdateChanges() {
                HashMap<String, String> map = new HashMap<String, String>();
                SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT);

                for (int i = 0; i < hr.getPropertyNames().length; i++) {
                  String propName = hr.getPropertyNames()[i];

                  if (propName.equals(STRUCTURE_NAMES[0])) {
                    map.put(
                        propName,
                        msg.getProtocolProviderService().getAccountID().getAccountUniqueID());
                  } else if (propName.equals(STRUCTURE_NAMES[1])) {
                    map.put(propName, msg.getContactAddress());
                  } else if (propName.equals(STRUCTURE_NAMES[2])) {
                    map.put(propName, sdf.format(msg.getTimestamp()));
                  } else if (propName.equals(STRUCTURE_NAMES[3]))
                    map.put(propName, RECENT_MSGS_VER);
                }

                return map;
              }
            });
      } catch (IOException ex) {
        logger.error("cannot create recent_messages history", ex);
        return;
      }
    }
  }

  @Override
  public void messageReceived(MessageReceivedEvent evt) {
    if (isSMSEnabled && evt.getEventType() != MessageReceivedEvent.SMS_MESSAGE_RECEIVED) {
      return;
    }

    handle(evt, evt.getSourceContact().getProtocolProvider(), evt.getSourceContact().getAddress());
  }

  @Override
  public void messageDelivered(MessageDeliveredEvent evt) {
    if (isSMSEnabled && !evt.isSmsMessage()) return;

    handle(
        evt,
        evt.getDestinationContact().getProtocolProvider(),
        evt.getDestinationContact().getAddress());
  }

  /**
   * Not used.
   *
   * @param evt the <tt>MessageFailedEvent</tt> containing the ID of the
   */
  @Override
  public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) {}

  @Override
  public void messageReceived(ChatRoomMessageReceivedEvent evt) {
    if (isSMSEnabled) return;

    // ignore non conversation messages
    if (evt.getEventType() != ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED) return;

    handle(
        evt, evt.getSourceChatRoom().getParentProvider(), evt.getSourceChatRoom().getIdentifier());
  }

  @Override
  public void messageDelivered(ChatRoomMessageDeliveredEvent evt) {
    if (isSMSEnabled) return;

    handle(
        evt, evt.getSourceChatRoom().getParentProvider(), evt.getSourceChatRoom().getIdentifier());
  }

  /**
   * Not used.
   *
   * @param evt the <tt>ChatroomMessageDeliveryFailedEvent</tt> containing
   */
  @Override
  public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) {}

  @Override
  public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) {
    // TODO
  }

  @Override
  public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) {
    // TODO
  }

  /**
   * Not used.
   *
   * @param evt the <tt>AdHocChatroomMessageDeliveryFailedEvent</tt>
   */
  @Override
  public void messageDeliveryFailed(AdHocChatRoomMessageDeliveryFailedEvent evt) {}

  @Override
  public void subscriptionCreated(SubscriptionEvent evt) {}

  @Override
  public void subscriptionFailed(SubscriptionEvent evt) {}

  @Override
  public void subscriptionRemoved(SubscriptionEvent evt) {}

  @Override
  public void subscriptionMoved(SubscriptionMovedEvent evt) {}

  @Override
  public void subscriptionResolved(SubscriptionEvent evt) {}

  /**
   * If a contact is renamed update the locally stored message if any.
   *
   * @param evt the <tt>ContactPropertyChangeEvent</tt> containing the source
   */
  @Override
  public void contactModified(ContactPropertyChangeEvent evt) {
    if (!evt.getPropertyName().equals(ContactPropertyChangeEvent.PROPERTY_DISPLAY_NAME)) return;

    Contact contact = evt.getSourceContact();

    if (contact == null) return;

    for (ComparableEvtObj msc : recentMessages) {
      if (contact.equals(msc.getContact())) {
        if (recentQuery != null)
          recentQuery.updateContactDisplayName(msc, contact.getDisplayName());

        return;
      }
    }
  }

  /**
   * Indicates that a MetaContact has been modified.
   *
   * @param evt the MetaContactListEvent containing the corresponding contact
   */
  public void metaContactRenamed(MetaContactRenamedEvent evt) {
    for (ComparableEvtObj msc : recentMessages) {
      if (evt.getSourceMetaContact().containsContact(msc.getContact())) {
        if (recentQuery != null) recentQuery.updateContactDisplayName(msc, evt.getNewDisplayName());
      }
    }
  }

  @Override
  public void supportedOperationSetsChanged(ContactCapabilitiesEvent event) {
    Contact contact = event.getSourceContact();

    if (contact == null) return;

    for (ComparableEvtObj msc : recentMessages) {
      if (contact.equals(msc.getContact())) {
        if (recentQuery != null) recentQuery.updateCapabilities(msc, contact);

        return;
      }
    }
  }

  /** Permanently removes all locally stored message history, remove recent contacts. */
  public void eraseLocallyStoredHistory() throws IOException {
    List<ComparableEvtObj> toRemove = null;
    synchronized (recentMessages) {
      toRemove = new ArrayList<ComparableEvtObj>(recentMessages);

      recentMessages.clear();
    }

    if (recentQuery != null) {
      for (ComparableEvtObj msc : toRemove) {
        recentQuery.fireContactRemoved(msc);
      }
    }
  }

  /**
   * Permanently removes locally stored message history for the metacontact, remove any recent
   * contacts if any.
   */
  public void eraseLocallyStoredHistory(MetaContact contact) throws IOException {
    List<ComparableEvtObj> toRemove = null;
    synchronized (recentMessages) {
      toRemove = new ArrayList<ComparableEvtObj>();
      Iterator<Contact> iter = contact.getContacts();
      while (iter.hasNext()) {
        Contact item = iter.next();
        String id = item.getAddress();
        ProtocolProviderService provider = item.getProtocolProvider();

        for (ComparableEvtObj msc : recentMessages) {
          if (msc.getProtocolProviderService().equals(provider)
              && msc.getContactAddress().equals(id)) {
            toRemove.add(msc);
          }
        }
      }

      recentMessages.removeAll(toRemove);
    }
    if (recentQuery != null) {
      for (ComparableEvtObj msc : toRemove) {
        recentQuery.fireContactRemoved(msc);
      }
    }
  }

  /**
   * Permanently removes locally stored message history for the chatroom, remove any recent contacts
   * if any.
   */
  public void eraseLocallyStoredHistory(ChatRoom room) {
    ComparableEvtObj toRemove = null;
    synchronized (recentMessages) {
      for (ComparableEvtObj msg : recentMessages) {
        if (msg.getRoom() != null && msg.getRoom().equals(room)) {
          toRemove = msg;
          break;
        }
      }

      if (toRemove == null) return;

      recentMessages.remove(toRemove);
    }

    if (recentQuery != null) recentQuery.fireContactRemoved(toRemove);
  }

  /** Object used to cache recent messages. */
  private class ComparableEvtObj implements Comparable<ComparableEvtObj> {
    private EventObject eventObject;

    /** The protocol provider. */
    private ProtocolProviderService ppService = null;

    /** The address. */
    private String address = null;

    /** The timestamp. */
    private Date timestamp = null;

    /** The contact instance. */
    private Contact contact = null;

    /** The room instance. */
    private ChatRoom room = null;

    /**
     * Constructs.
     *
     * @param source used to extract initial values.
     */
    ComparableEvtObj(EventObject source) {
      update(source);
    }

    /**
     * Extract values from <tt>EventObject</tt>.
     *
     * @param source
     */
    public void update(EventObject source) {
      this.eventObject = source;

      if (source instanceof MessageDeliveredEvent) {
        MessageDeliveredEvent e = (MessageDeliveredEvent) source;

        this.contact = e.getDestinationContact();

        this.address = contact.getAddress();
        this.ppService = contact.getProtocolProvider();
        this.timestamp = e.getTimestamp();
      } else if (source instanceof MessageReceivedEvent) {
        MessageReceivedEvent e = (MessageReceivedEvent) source;

        this.contact = e.getSourceContact();

        this.address = contact.getAddress();
        this.ppService = contact.getProtocolProvider();
        this.timestamp = e.getTimestamp();
      } else if (source instanceof ChatRoomMessageDeliveredEvent) {
        ChatRoomMessageDeliveredEvent e = (ChatRoomMessageDeliveredEvent) source;

        this.room = e.getSourceChatRoom();

        this.address = room.getIdentifier();
        this.ppService = room.getParentProvider();
        this.timestamp = e.getTimestamp();
      } else if (source instanceof ChatRoomMessageReceivedEvent) {
        ChatRoomMessageReceivedEvent e = (ChatRoomMessageReceivedEvent) source;

        this.room = e.getSourceChatRoom();

        this.address = room.getIdentifier();
        this.ppService = room.getParentProvider();
        this.timestamp = e.getTimestamp();
      }
    }

    @Override
    public String toString() {
      return "ComparableEvtObj{" + "address='" + address + '\'' + ", ppService=" + ppService + '}';
    }

    /**
     * The timestamp of the message.
     *
     * @return the timestamp of the message.
     */
    public Date getTimestamp() {
      return timestamp;
    }

    /**
     * The contact.
     *
     * @return the contact.
     */
    public Contact getContact() {
      return contact;
    }

    /**
     * The room.
     *
     * @return the room.
     */
    public ChatRoom getRoom() {
      return room;
    }

    /**
     * The protocol provider.
     *
     * @return the protocol provider.
     */
    public ProtocolProviderService getProtocolProviderService() {
      return ppService;
    }

    /**
     * The address.
     *
     * @return the address.
     */
    public String getContactAddress() {
      if (this.address != null) return this.address;

      return null;
    }

    /**
     * The event object.
     *
     * @return the event object.
     */
    public EventObject getEventObject() {
      return eventObject;
    }

    /**
     * Compares two ComparableEvtObj.
     *
     * @param o the object to compare with
     * @return 0, less than zero, greater than zero, if equals, less or greater.
     */
    @Override
    public int compareTo(ComparableEvtObj o) {
      if (o == null || o.getTimestamp() == null) return 1;

      return o.getTimestamp().compareTo(getTimestamp());
    }

    /**
     * Checks if equals, and if this event object is used to create a MessageSourceContact, if the
     * supplied <tt>Object</tt> is instance of MessageSourceContact.
     *
     * @param o the object to check.
     * @return <tt>true</tt> if equals.
     */
    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || (!(o instanceof MessageSourceContact) && getClass() != o.getClass()))
        return false;

      if (o instanceof ComparableEvtObj) {
        ComparableEvtObj that = (ComparableEvtObj) o;

        if (!address.equals(that.address)) return false;
        if (!ppService.equals(that.ppService)) return false;
      } else if (o instanceof MessageSourceContact) {
        MessageSourceContact that = (MessageSourceContact) o;

        if (!address.equals(that.getContactAddress())) return false;
        if (!ppService.equals(that.getProtocolProviderService())) return false;
      } else return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = address.hashCode();
      result = 31 * result + ppService.hashCode();
      return result;
    }
  }
}
示例#5
0
/**
 * The contactlist panel not only contains the contact list but it has the role of a message
 * dispatcher. It process all sent and received messages as well as all typing notifications. Here
 * are managed all contact list mouse events.
 *
 * @author Yana Stamcheva
 * @author Hristo Terezov
 */
public class ContactListPane extends SIPCommScrollPane
    implements MessageListener,
        TypingNotificationsListener,
        FileTransferListener,
        ContactListListener,
        PluginComponentListener {
  /** Serial version UID. */
  private static final long serialVersionUID = 0L;

  private final MainFrame mainFrame;

  private TreeContactList contactList;

  private final TypingTimer typingTimer = new TypingTimer();

  private CommonRightButtonMenu commonRightButtonMenu;

  private final Logger logger = Logger.getLogger(ContactListPane.class);

  private final ChatWindowManager chatWindowManager;

  /**
   * Creates the contactlist scroll panel defining the parent frame.
   *
   * @param mainFrame The parent frame.
   */
  public ContactListPane(MainFrame mainFrame) {
    this.mainFrame = mainFrame;

    this.chatWindowManager = GuiActivator.getUIService().getChatWindowManager();

    this.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

    this.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.GRAY));

    this.initPluginComponents();
  }

  /**
   * Initializes the contact list.
   *
   * @param contactListService The MetaContactListService which will be used for a contact list data
   *     model.
   */
  public void initList(MetaContactListService contactListService) {
    this.contactList = new TreeContactList(mainFrame);
    // We should first set the contact list to the GuiActivator, so that
    // anybody could get it from there.
    GuiActivator.setContactList(contactList);

    // By default we set the current filter to be the presence filter.
    contactList.applyFilter(TreeContactList.presenceFilter);

    TransparentPanel transparentPanel = new TransparentPanel(new BorderLayout());

    transparentPanel.add(contactList, BorderLayout.NORTH);

    this.setViewportView(transparentPanel);

    transparentPanel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
    this.contactList.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));

    this.contactList.addContactListListener(this);
    this.addMouseListener(
        new MouseAdapter() {
          @Override
          public void mousePressed(MouseEvent e) {
            if ((e.getModifiers() & InputEvent.BUTTON3_MASK) != 0) {
              commonRightButtonMenu = new CommonRightButtonMenu(mainFrame);

              commonRightButtonMenu.setInvoker(ContactListPane.this);

              commonRightButtonMenu.setLocation(
                  e.getX() + mainFrame.getX() + 5, e.getY() + mainFrame.getY() + 105);

              commonRightButtonMenu.setVisible(true);
            }
          }
        });
  }

  /**
   * Returns the contact list.
   *
   * @return the contact list
   */
  public TreeContactList getContactList() {
    return this.contactList;
  }

  /**
   * Implements the ContactListListener.contactSelected method.
   *
   * @param evt the <tt>ContactListEvent</tt> that notified us
   */
  public void contactClicked(ContactListEvent evt) {
    // We're interested only in two click events.
    if (evt.getClickCount() < 2) return;

    UIContact descriptor = evt.getSourceContact();

    // We're currently only interested in MetaContacts.
    if (descriptor.getDescriptor() instanceof MetaContact) {
      MetaContact metaContact = (MetaContact) descriptor.getDescriptor();

      // Searching for the right proto contact to use as default for the
      // chat conversation.
      Contact defaultContact =
          metaContact.getDefaultContact(OperationSetBasicInstantMessaging.class);

      // do nothing
      if (defaultContact == null) {
        defaultContact = metaContact.getDefaultContact(OperationSetSmsMessaging.class);

        if (defaultContact == null) return;
      }

      ProtocolProviderService defaultProvider = defaultContact.getProtocolProvider();

      OperationSetBasicInstantMessaging defaultIM =
          defaultProvider.getOperationSet(OperationSetBasicInstantMessaging.class);

      ProtocolProviderService protoContactProvider;
      OperationSetBasicInstantMessaging protoContactIM;

      boolean isOfflineMessagingSupported =
          defaultIM != null && !defaultIM.isOfflineMessagingSupported();

      if (defaultContact.getPresenceStatus().getStatus() < 1
          && (!isOfflineMessagingSupported || !defaultProvider.isRegistered())) {
        Iterator<Contact> protoContacts = metaContact.getContacts();

        while (protoContacts.hasNext()) {
          Contact contact = protoContacts.next();

          protoContactProvider = contact.getProtocolProvider();

          protoContactIM =
              protoContactProvider.getOperationSet(OperationSetBasicInstantMessaging.class);

          if (protoContactIM != null
              && protoContactIM.isOfflineMessagingSupported()
              && protoContactProvider.isRegistered()) {
            defaultContact = contact;
          }
        }
      }

      ContactEventHandler contactHandler =
          mainFrame.getContactHandler(defaultContact.getProtocolProvider());

      contactHandler.contactClicked(defaultContact, evt.getClickCount());
    } else if (descriptor.getDescriptor() instanceof SourceContact) {
      SourceContact contact = (SourceContact) descriptor.getDescriptor();

      List<ContactDetail> imDetails =
          contact.getContactDetails(OperationSetBasicInstantMessaging.class);
      List<ContactDetail> mucDetails = contact.getContactDetails(OperationSetMultiUserChat.class);

      if (imDetails != null && imDetails.size() > 0) {
        ProtocolProviderService pps =
            imDetails.get(0).getPreferredProtocolProvider(OperationSetBasicInstantMessaging.class);

        GuiActivator.getUIService()
            .getChatWindowManager()
            .startChat(contact.getContactAddress(), pps);
      } else if (mucDetails != null && mucDetails.size() > 0) {
        ChatRoomWrapper room =
            GuiActivator.getMUCService().findChatRoomWrapperFromSourceContact(contact);

        if (room == null) {
          // lets check by id
          ProtocolProviderService pps =
              mucDetails.get(0).getPreferredProtocolProvider(OperationSetMultiUserChat.class);

          room =
              GuiActivator.getMUCService()
                  .findChatRoomWrapperFromChatRoomID(contact.getContactAddress(), pps);

          if (room == null) {
            GuiActivator.getMUCService()
                .createChatRoom(
                    contact.getContactAddress(),
                    pps,
                    new ArrayList<String>(),
                    "",
                    false,
                    false,
                    false);
          }
        }

        if (room != null) GuiActivator.getMUCService().openChatRoom(room);
      } else {
        List<ContactDetail> smsDetails = contact.getContactDetails(OperationSetSmsMessaging.class);

        if (smsDetails != null && smsDetails.size() > 0) {
          GuiActivator.getUIService()
              .getChatWindowManager()
              .startChat(contact.getContactAddress(), true);
        }
      }
    }
  }

  /**
   * Implements the ContactListListener.groupSelected method.
   *
   * @param evt the <tt>ContactListEvent</tt> that notified us
   */
  public void groupClicked(ContactListEvent evt) {}

  /** We're not interested in group selection events here. */
  public void groupSelected(ContactListEvent evt) {}

  /** We're not interested in contact selection events here. */
  public void contactSelected(ContactListEvent evt) {}

  /**
   * When a message is received determines whether to open a new chat window or chat window tab, or
   * to indicate that a message is received from a contact which already has an open chat. When the
   * chat is found checks if in mode "Auto popup enabled" and if this is the case shows the message
   * in the appropriate chat panel.
   *
   * @param evt the event containing details on the received message
   */
  public void messageReceived(MessageReceivedEvent evt) {
    if (logger.isTraceEnabled())
      logger.trace("MESSAGE RECEIVED from contact: " + evt.getSourceContact().getAddress());

    Contact protocolContact = evt.getSourceContact();
    ContactResource contactResource = evt.getContactResource();
    Message message = evt.getSourceMessage();
    int eventType = evt.getEventType();
    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(protocolContact);

    if (metaContact != null) {
      messageReceived(
          protocolContact,
          contactResource,
          metaContact,
          message,
          eventType,
          evt.getTimestamp(),
          evt.getCorrectedMessageUID(),
          evt.isPrivateMessaging(),
          evt.getPrivateMessagingContactRoom());
    } else {
      if (logger.isTraceEnabled())
        logger.trace("MetaContact not found for protocol contact: " + protocolContact + ".");
    }
  }

  /**
   * When a message is received determines whether to open a new chat window or chat window tab, or
   * to indicate that a message is received from a contact which already has an open chat. When the
   * chat is found checks if in mode "Auto popup enabled" and if this is the case shows the message
   * in the appropriate chat panel.
   *
   * @param protocolContact the source contact of the event
   * @param contactResource the resource from which the contact is writing
   * @param metaContact the metacontact containing <tt>protocolContact</tt>
   * @param message the message to deliver
   * @param eventType the event type
   * @param timestamp the timestamp of the event
   * @param correctedMessageUID the identifier of the corrected message
   * @param isPrivateMessaging if <tt>true</tt> the message is received from private messaging
   *     contact.
   * @param privateContactRoom the chat room associated with the private messaging contact.
   */
  private void messageReceived(
      final Contact protocolContact,
      final ContactResource contactResource,
      final MetaContact metaContact,
      final Message message,
      final int eventType,
      final Date timestamp,
      final String correctedMessageUID,
      final boolean isPrivateMessaging,
      final ChatRoom privateContactRoom) {
    if (!SwingUtilities.isEventDispatchThread()) {
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              messageReceived(
                  protocolContact,
                  contactResource,
                  metaContact,
                  message,
                  eventType,
                  timestamp,
                  correctedMessageUID,
                  isPrivateMessaging,
                  privateContactRoom);
            }
          });
      return;
    }

    // Obtain the corresponding chat panel.
    final ChatPanel chatPanel =
        chatWindowManager.getContactChat(
            metaContact, protocolContact, contactResource, message.getMessageUID());

    // Show an envelope on the sender contact in the contact list and
    // in the systray.
    if (!chatPanel.isChatFocused()) contactList.setActiveContact(metaContact, true);

    // Distinguish the message type, depending on the type of event that
    // we have received.
    String messageType = null;

    if (eventType == MessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED) {
      messageType = Chat.INCOMING_MESSAGE;
    } else if (eventType == MessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED) {
      messageType = Chat.SYSTEM_MESSAGE;
    } else if (eventType == MessageReceivedEvent.SMS_MESSAGE_RECEIVED) {
      messageType = Chat.SMS_MESSAGE;
    }

    String contactAddress =
        (contactResource != null)
            ? protocolContact.getAddress() + " (" + contactResource.getResourceName() + ")"
            : protocolContact.getAddress();

    chatPanel.addMessage(
        contactAddress,
        protocolContact.getDisplayName(),
        timestamp,
        messageType,
        message.getContent(),
        message.getContentType(),
        message.getMessageUID(),
        correctedMessageUID);

    String resourceName = (contactResource != null) ? contactResource.getResourceName() : null;

    if (isPrivateMessaging) {
      chatWindowManager.openPrivateChatForChatRoomMember(privateContactRoom, protocolContact);
    } else {
      chatWindowManager.openChat(chatPanel, false);
    }

    ChatTransport chatTransport =
        chatPanel.getChatSession().findChatTransportForDescriptor(protocolContact, resourceName);

    chatPanel.setSelectedChatTransport(chatTransport, true);
  }

  /**
   * When a sent message is delivered shows it in the chat conversation panel.
   *
   * @param evt the event containing details on the message delivery
   */
  public void messageDelivered(MessageDeliveredEvent evt) {
    Contact contact = evt.getDestinationContact();
    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(contact);

    if (logger.isTraceEnabled())
      logger.trace("MESSAGE DELIVERED to contact: " + contact.getAddress());

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

    if (chatPanel != null) {
      Message msg = evt.getSourceMessage();
      ProtocolProviderService protocolProvider = contact.getProtocolProvider();

      if (logger.isTraceEnabled())
        logger.trace(
            "MESSAGE DELIVERED: process message to chat for contact: "
                + contact.getAddress()
                + " MESSAGE: "
                + msg.getContent());

      chatPanel.addMessage(
          this.mainFrame.getAccountAddress(protocolProvider),
          this.mainFrame.getAccountDisplayName(protocolProvider),
          evt.getTimestamp(),
          Chat.OUTGOING_MESSAGE,
          msg.getContent(),
          msg.getContentType(),
          msg.getMessageUID(),
          evt.getCorrectedMessageUID());

      if (evt.isSmsMessage() && !ConfigurationUtils.isSmsNotifyTextDisabled()) {
        chatPanel.addMessage(
            contact.getDisplayName(),
            new Date(),
            Chat.ACTION_MESSAGE,
            GuiActivator.getResources().getI18NString("service.gui.SMS_SUCCESSFULLY_SENT"),
            "text");
      }
    }
  }

  /**
   * Shows a warning message to the user when message delivery has failed.
   *
   * @param evt the event containing details on the message delivery failure
   */
  public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) {
    logger.error(evt.getReason());

    String errorMsg = null;

    Message sourceMessage = (Message) evt.getSource();

    Contact sourceContact = evt.getDestinationContact();

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(sourceContact);

    if (evt.getErrorCode() == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED) {
      errorMsg =
          GuiActivator.getResources()
              .getI18NString(
                  "service.gui.MSG_DELIVERY_NOT_SUPPORTED",
                  new String[] {sourceContact.getDisplayName()});
    } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.NETWORK_FAILURE) {
      errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_NOT_DELIVERED");
    } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED) {
      errorMsg =
          GuiActivator.getResources().getI18NString("service.gui.MSG_SEND_CONNECTION_PROBLEM");
    } else if (evt.getErrorCode() == MessageDeliveryFailedEvent.INTERNAL_ERROR) {
      errorMsg =
          GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_INTERNAL_ERROR");
    } else {
      errorMsg = GuiActivator.getResources().getI18NString("service.gui.MSG_DELIVERY_ERROR");
    }

    String reason = evt.getReason();
    if (reason != null)
      errorMsg +=
          " "
              + GuiActivator.getResources()
                  .getI18NString("service.gui.ERROR_WAS", new String[] {reason});

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, sourceContact);

    chatPanel.addMessage(
        sourceContact.getAddress(),
        metaContact.getDisplayName(),
        new Date(),
        Chat.OUTGOING_MESSAGE,
        sourceMessage.getContent(),
        sourceMessage.getContentType(),
        sourceMessage.getMessageUID(),
        evt.getCorrectedMessageUID());

    chatPanel.addErrorMessage(metaContact.getDisplayName(), errorMsg);

    chatWindowManager.openChat(chatPanel, false);
  }

  /**
   * Informs the user what is the typing state of his chat contacts.
   *
   * @param evt the event containing details on the typing notification
   */
  public void typingNotificationReceived(TypingNotificationEvent evt) {
    if (typingTimer.isRunning()) typingTimer.stop();

    String notificationMsg = "";

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(evt.getSourceContact());
    String contactName = metaContact.getDisplayName() + " ";

    if (contactName.equals("")) {
      contactName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN") + " ";
    }

    int typingState = evt.getTypingState();

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

    if (typingState == OperationSetTypingNotifications.STATE_TYPING) {
      notificationMsg =
          GuiActivator.getResources()
              .getI18NString("service.gui.CONTACT_TYPING", new String[] {contactName});

      // Proactive typing notification
      if (!chatWindowManager.isChatOpenedFor(metaContact)) {
        return;
      }

      if (chatPanel != null) chatPanel.addTypingNotification(notificationMsg);

      typingTimer.setMetaContact(metaContact);
      typingTimer.start();
    } else if (typingState == OperationSetTypingNotifications.STATE_PAUSED) {
      notificationMsg =
          GuiActivator.getResources()
              .getI18NString("service.gui.CONTACT_PAUSED_TYPING", new String[] {contactName});

      if (chatPanel != null) chatPanel.addTypingNotification(notificationMsg);

      typingTimer.setMetaContact(metaContact);
      typingTimer.start();
    } else {
      if (chatPanel != null) chatPanel.removeTypingNotification();
    }
  }

  /**
   * Called to indicate that sending typing notification has failed.
   *
   * @param evt a <tt>TypingNotificationEvent</tt> containing the sender of the notification and its
   *     type.
   */
  public void typingNotificationDeliveryFailed(TypingNotificationEvent evt) {
    if (typingTimer.isRunning()) typingTimer.stop();

    String notificationMsg = "";

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(evt.getSourceContact());
    String contactName = metaContact.getDisplayName();

    if (contactName.equals("")) {
      contactName = GuiActivator.getResources().getI18NString("service.gui.UNKNOWN") + " ";
    }

    ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

    notificationMsg =
        GuiActivator.getResources()
            .getI18NString("service.gui.CONTACT_TYPING_SEND_FAILED", new String[] {contactName});

    // Proactive typing notification
    if (!chatWindowManager.isChatOpenedFor(metaContact)) {
      return;
    }

    if (chatPanel != null) chatPanel.addErrorSendingTypingNotification(notificationMsg);

    typingTimer.setMetaContact(metaContact);
    typingTimer.start();
  }

  /**
   * When a request has been received we show it to the user through the chat session renderer.
   *
   * @param event <tt>FileTransferRequestEvent</tt>
   * @see FileTransferListener#fileTransferRequestReceived(FileTransferRequestEvent)
   */
  public void fileTransferRequestReceived(FileTransferRequestEvent event) {
    IncomingFileTransferRequest request = event.getRequest();

    Contact sourceContact = request.getSender();

    MetaContact metaContact =
        GuiActivator.getContactListService().findMetaContactByContact(sourceContact);

    final ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, sourceContact);

    chatPanel.addIncomingFileTransferRequest(
        event.getFileTransferOperationSet(), request, event.getTimestamp());

    ChatTransport chatTransport =
        chatPanel.getChatSession().findChatTransportForDescriptor(sourceContact, null);

    chatPanel.setSelectedChatTransport(chatTransport, true);

    // Opens the chat panel with the new message in the UI thread.
    chatWindowManager.openChat(chatPanel, false);
  }

  /**
   * Nothing to do here, because we already know when a file transfer is created.
   *
   * @param event the <tt>FileTransferCreatedEvent</tt> that notified us
   */
  public void fileTransferCreated(FileTransferCreatedEvent event) {}

  /**
   * Called when a new <tt>IncomingFileTransferRequest</tt> has been rejected. Nothing to do here,
   * because we are the one who rejects the request.
   *
   * @param event the <tt>FileTransferRequestEvent</tt> containing the received request which was
   *     rejected.
   */
  public void fileTransferRequestRejected(FileTransferRequestEvent event) {}

  /**
   * Called when an <tt>IncomingFileTransferRequest</tt> has been canceled from the contact who sent
   * it.
   *
   * @param event the <tt>FileTransferRequestEvent</tt> containing the request which was canceled.
   */
  public void fileTransferRequestCanceled(FileTransferRequestEvent event) {}

  /**
   * Returns the right button menu of the contact list.
   *
   * @return the right button menu of the contact list
   */
  public CommonRightButtonMenu getCommonRightButtonMenu() {
    return commonRightButtonMenu;
  }

  /**
   * The TypingTimer is started after a PAUSED typing notification is received. It waits 5 seconds
   * and if no other typing event occurs removes the PAUSED message from the chat status panel.
   */
  private class TypingTimer extends Timer {
    /** Serial version UID. */
    private static final long serialVersionUID = 0L;

    private MetaContact metaContact;

    public TypingTimer() {
      // Set delay
      super(5 * 1000, null);

      this.addActionListener(new TimerActionListener());
    }

    private class TimerActionListener implements ActionListener {
      public void actionPerformed(ActionEvent e) {
        ChatPanel chatPanel = chatWindowManager.getContactChat(metaContact, false);

        if (chatPanel != null) chatPanel.removeTypingNotification();
      }
    }

    private void setMetaContact(MetaContact metaContact) {
      this.metaContact = metaContact;
    }
  }

  private void initPluginComponents() {
    // Search for plugin components registered through the OSGI bundle
    // context.
    ServiceReference[] serRefs = null;

    String osgiFilter =
        "(" + Container.CONTAINER_ID + "=" + Container.CONTAINER_CONTACT_LIST.getID() + ")";

    try {
      serRefs =
          GuiActivator.bundleContext.getServiceReferences(
              PluginComponentFactory.class.getName(), osgiFilter);
    } catch (InvalidSyntaxException exc) {
      logger.error("Could not obtain plugin reference.", exc);
    }

    if (serRefs != null) {
      for (ServiceReference serRef : serRefs) {
        PluginComponentFactory factory =
            (PluginComponentFactory) GuiActivator.bundleContext.getService(serRef);
        PluginComponent component = factory.getPluginComponentInstance(this);

        Object selectedValue = getContactList().getSelectedValue();

        if (selectedValue instanceof MetaContact) {
          component.setCurrentContact((MetaContact) selectedValue);
        } else if (selectedValue instanceof MetaContactGroup) {
          component.setCurrentContactGroup((MetaContactGroup) selectedValue);
        }

        String pluginConstraints = factory.getConstraints();
        Object constraints;

        if (pluginConstraints != null)
          constraints = UIServiceImpl.getBorderLayoutConstraintsFromContainer(pluginConstraints);
        else constraints = BorderLayout.SOUTH;

        this.add((Component) component.getComponent(), constraints);

        this.repaint();
      }
    }

    GuiActivator.getUIService().addPluginComponentListener(this);
  }

  /**
   * Adds the plugin component given by <tt>event</tt> to this panel if it's its container.
   *
   * @param event the <tt>PluginComponentEvent</tt> that notified us
   */
  public void pluginComponentAdded(PluginComponentEvent event) {
    PluginComponentFactory factory = event.getPluginComponentFactory();

    // If the container id doesn't correspond to the id of the plugin
    // container we're not interested.
    if (!factory.getContainer().equals(Container.CONTAINER_CONTACT_LIST)) return;

    Object constraints =
        UIServiceImpl.getBorderLayoutConstraintsFromContainer(factory.getConstraints());

    if (constraints == null) constraints = BorderLayout.SOUTH;

    PluginComponent pluginComponent = factory.getPluginComponentInstance(this);
    this.add((Component) pluginComponent.getComponent(), constraints);

    Object selectedValue = getContactList().getSelectedValue();

    if (selectedValue instanceof MetaContact) {
      pluginComponent.setCurrentContact((MetaContact) selectedValue);
    } else if (selectedValue instanceof MetaContactGroup) {
      pluginComponent.setCurrentContactGroup((MetaContactGroup) selectedValue);
    }

    this.revalidate();
    this.repaint();
  }

  /**
   * Removes the plugin component given by <tt>event</tt> if previously added in this panel.
   *
   * @param event the <tt>PluginComponentEvent</tt> that notified us
   */
  public void pluginComponentRemoved(PluginComponentEvent event) {
    PluginComponentFactory factory = event.getPluginComponentFactory();

    // If the container id doesn't correspond to the id of the plugin
    // container we're not interested.
    if (!factory.getContainer().equals(Container.CONTAINER_CONTACT_LIST)) return;

    this.remove((Component) factory.getPluginComponentInstance(this).getComponent());
  }
}