/**
   * 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);
        }
      }
    }
  }
    /**
     * 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;
    }
  /**
   * 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);
    }
  }
  /**
   * 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));
    }
  }