@SuppressWarnings("unchecked")
  private void garbageCollectIdentities() {
    final MessageManager messageManager = mFreetalk.getMessageManager();
    final PersistentTaskManager taskManager = mFreetalk.getTaskManager();

    synchronized (this) {
      if (mIdentityFetchInProgress
          || mOwnIdentityFetchInProgress
          || mLastIdentityFetchTime == 0
          || mLastOwnIdentityFetchTime == 0) return;

      /* Executing the thread loop once will always take longer than THREAD_PERIOD. Therefore, if we set the limit to 3*THREAD_PERIOD,
       * it will hit identities which were last received before more than 2*THREAD_LOOP, not exactly 3*THREAD_LOOP. */
      long lastAcceptTime =
          Math.min(mLastIdentityFetchTime, mLastOwnIdentityFetchTime) - GARBAGE_COLLECT_DELAY;
      lastAcceptTime =
          Math.max(
              lastAcceptTime,
              0); // This is not really needed but a time less than 0 does not make sense.;

      Query q = db.query();
      q.constrain(WoTIdentity.class);
      q.descend("mLastReceivedFromWoT").constrain(lastAcceptTime).smaller();
      ObjectSet<WoTIdentity> result = q.execute();

      for (WoTIdentity identity : result) {
        identity.initializeTransient(mFreetalk);
        Logger.debug(this, "Garbage collecting identity " + identity);
        deleteIdentity(identity, messageManager, taskManager);
      }

      if (mShortestUniqueNicknameCacheNeedsUpdate) updateShortestUniqueNicknameCache();
    }
  }
  /**
   * Checks for duplicate identity objects and deletes duplicates if they exist. I have absolutely
   * NO idea why Bombe does happen to have a duplicate identity, I see no code path which could
   * cause this. TODO: Get rid of this function if nobody reports a duplicate for some time - the
   * function was added at 2011-01-10
   */
  private synchronized void deleteDuplicateIdentities() {
    WoTMessageManager messageManager = mFreetalk.getMessageManager();
    PersistentTaskManager taskManager = mFreetalk.getTaskManager();

    synchronized (messageManager) {
      synchronized (taskManager) {
        synchronized (db.lock()) {
          try {
            HashSet<String> deleted = new HashSet<String>();

            Logger.normal(this, "Searching for duplicate identities ...");

            for (WoTIdentity identity : getAllIdentities()) {
              Query q = db.query();
              q.constrain(WoTIdentity.class);
              q.descend("mID").constrain(identity.getID());
              q.constrain(identity).identity().not();
              ObjectSet<WoTIdentity> duplicates =
                  new Persistent.InitializingObjectSet<WoTIdentity>(mFreetalk, q);

              for (WoTIdentity duplicate : duplicates) {
                if (deleted.contains(duplicate.getID()) == false) {
                  Logger.error(
                      duplicate, "Deleting duplicate identity " + duplicate.getRequestURI());
                  deleteIdentity(duplicate, messageManager, taskManager);
                }
              }
              deleted.add(identity.getID());
            }
            Persistent.checkedCommit(db, this);

            Logger.normal(this, "Finished searching for duplicate identities.");
          } catch (RuntimeException e) {
            Persistent.checkedRollback(db, this, e);
          }
        }
      }
    }
  }
  @SuppressWarnings("unchecked")
  private void parseIdentities(SimpleFieldSet params, boolean bOwnIdentities) {
    if (bOwnIdentities) Logger.normal(this, "Parsing received own identities...");
    else Logger.normal(this, "Parsing received identities...");

    int idx;
    int ignoredCount = 0;
    int newCount = 0;

    for (idx = 0; ; idx++) {
      String identityID = params.get("Identity" + idx);
      if (identityID == null
          || identityID.equals("")) /* TODO: Figure out whether the second condition is necessary */
        break;
      String requestURI = params.get("RequestURI" + idx);
      String insertURI = bOwnIdentities ? params.get("InsertURI" + idx) : null;
      String nickname = params.get("Nickname" + idx);

      if (nickname == null || nickname.length() == 0) {
        // If an identity publishes an invalid nickname in one of its first WoT inserts then WoT
        // will return an empty
        // nickname for that identity until a new XML was published with a valid nickname. We ignore
        // the identity until
        // then to prevent confusing error logs.
        // TODO: Maybe handle this in WoT. Would require checks in many places though.
        continue;
      }

      synchronized (this) {
          /* We lock here and not during the whole function to allow other threads to execute */
        Query q = db.query(); // TODO: Encapsulate the query in a function...
        q.constrain(WoTIdentity.class);
        q.descend("mID").constrain(identityID);
        ObjectSet<WoTIdentity> result = q.execute();
        WoTIdentity id = null;

        if (result.size() == 0) {
          try {
            importIdentity(bOwnIdentities, identityID, requestURI, insertURI, nickname);
            ++newCount;
          } catch (Exception e) {
            Logger.error(this, "Importing a new identity failed.", e);
          }
        } else {
          Logger.debug(this, "Not importing already existing identity " + requestURI);
          ++ignoredCount;

          assert (result.size() == 1);
          id = result.next();
          id.initializeTransient(mFreetalk);

          if (bOwnIdentities != (id instanceof WoTOwnIdentity)) {
            // The type of the identity changed so we need to delete and re-import it.

            try {
              Logger.normal(this, "Identity type changed, replacing it: " + id);
              // We MUST NOT take the following locks because deleteIdentity does other locks
              // (MessageManager/TaskManager) which must happen before...
              // synchronized(id)
              // synchronized(db.lock())
              deleteIdentity(id, mFreetalk.getMessageManager(), mFreetalk.getTaskManager());
              importIdentity(bOwnIdentities, identityID, requestURI, insertURI, nickname);
            } catch (Exception e) {
              Logger.error(this, "Replacing a WoTIdentity with WoTOwnIdentity failed.", e);
            }

          } else { // Normal case: Update the last received time of the idefnt
            synchronized (id) {
              synchronized (db.lock()) {
                try {
                  // TODO: The thread sometimes takes hours to parse the identities and I don't know
                  // why.
                  // So right now its better to re-query the time for each identity.
                  id.setLastReceivedFromWoT(CurrentTimeUTC.getInMillis());
                  id.checkedCommit(this);
                } catch (Exception e) {
                  Persistent.checkedRollback(db, this, e);
                }
              }
            }
          }
        }
      }

      Thread.yield();
    }

    Logger.normal(
        this,
        "parseIdentities(bOwnIdentities=="
            + bOwnIdentities
            + " received "
            + idx
            + " identities. Ignored "
            + ignoredCount
            + "; New: "
            + newCount);
  }