/**
  * Adds a hook to the HookManager. You can associate more than one hook with a given message type
  * which will be returned in order by getHooks().
  *
  * @param msgType the message type to match
  * @param hook the hook to be called for matching messages
  * @see EnginePlugin#handleMessageImpl
  */
 public void addHook(MessageType msgType, Hook hook) {
   lock.lock();
   try {
     List<Hook> hookList = hooks.get(msgType);
     if (hookList == null) {
       hookList = new LinkedList<Hook>();
       hookList.add(hook);
       hooks.put(msgType, hookList);
     } else {
       hookList = new LinkedList<Hook>(hookList);
       hookList.add(hook);
       hooks.put(msgType, hookList);
     }
   } finally {
     lock.unlock();
   }
 }
  /**
   * Track union of perceived objects and keep filter in-sync. The filter must be a {@link
   * PerceptionFilter} and the message must be a {@link PerceptionMessage}.
   *
   * @param triggeringMessage The matched message.
   * @param triggeringFilter The matched filter.
   * @param agent The local message agent.
   */
  public synchronized void trigger(
      Message triggeringMessage, IFilter triggeringFilter, MessageAgent agent) {
    PerceptionMessage message = (PerceptionMessage) triggeringMessage;

    List<ObjectNote> gainObjects = message.getGainObjects();
    List<ObjectNote> lostObjects = message.getLostObjects();

    if (gainObjects != null) {
      List<PerceptionFilter.TypedSubject> newSubjects =
          new ArrayList<PerceptionFilter.TypedSubject>(gainObjects.size());

      for (ObjectNote gain : gainObjects) {
        IntHolder refCount = objectRefs.get(gain.subjectOid);
        if (refCount == null) {
          objectRefs.put(gain.subjectOid, new IntHolder(1));
          newSubjects.add(new PerceptionFilter.TypedSubject(gain.subjectOid, gain.objectType));
        } else refCount.count++;
      }

      if (newSubjects.size() > 0) {
        if (Log.loggingDebug) Log.debug("PerceptionTrigger adding " + newSubjects.size());
        filter.addSubjects(newSubjects);
      }
    }

    if (lostObjects == null) return;

    List<Long> freeOids = new ArrayList<Long>(lostObjects.size());

    for (ObjectNote lost : lostObjects) {
      IntHolder refCount = objectRefs.get(lost.subjectOid);
      if (refCount == null) Log.error("PerceptionTrigger: duplicate lost " + lost.subjectOid);
      else if (refCount.count == 1) {
        objectRefs.remove(lost.subjectOid);
        freeOids.add(lost.subjectOid);
      } else refCount.count--;
    }

    if (freeOids.size() > 0) {
      if (Log.loggingDebug) Log.debug("PerceptionTrigger removing " + freeOids.size());
      filter.removeSubjects(freeOids);
    }
  }
  /** for client display: current state */
  public List<String> getObjectiveStatus() {
    lock.lock();
    try {
      List<String> l = new LinkedList<String>();

      Iterator<CollectionGoalStatus> iter = goalsStatus.iterator();
      while (iter.hasNext()) {
        CollectionGoalStatus status = iter.next();
        String itemName = status.getTemplateName();
        int numNeeded = status.targetCount;
        int cur = Math.min(status.currentCount, numNeeded);

        String objective = itemName + ": " + cur + "/" + numNeeded;
        l.add(objective);
      }
      return l;
    } finally {
      lock.unlock();
    }
  }