private void remove(LinkableEvaluation eval) {
    LinkableEvaluation prev = eval.previous;
    LinkableEvaluation next = eval.next;

    if (prev != null) prev.next = next;
    else pendingEvaluationHead = next;

    if (next != null) next.previous = prev;
    else pendingEvaluationTail = prev;
  }
 private void finished() {
   if (!finished) {
     finished = true;
     for (LinkableEvaluation pendingEval = pendingEvaluationHead;
         pendingEval != null;
         pendingEval = pendingEval.next) pendingEval.removeListener(this);
     pendingEvaluationHead = pendingEvaluationTail = null;
     fireFinished();
   }
 }
 @Override
 protected void dispose() {
   if (nodeSetListener != null) {
     for (LongTreeMap.Entry<Object> entry = result.firstEntry();
         entry != null;
         entry = entry.next()) nodeSetListener.discard(entry.getKey());
   }
   manuallyExpired = true;
   for (LinkableEvaluation pendingEval = pendingEvaluationHead;
       pendingEval != null;
       pendingEval = pendingEval.next) pendingEval.removeListener(this);
   pendingEvaluationHead = pendingEvaluationTail = null;
   if (predicateResult == null) predicateEvaluation.removeListener(this);
 }
  private void resultPrepared() {
    if (!resultPrepared) {
      manuallyExpired = true;
      resultPrepared = true;

      for (LinkableEvaluation pendingEval = pendingEvaluationHead;
          pendingEval != null;
          pendingEval = pendingEval.next) pendingEval.removeListener(this);
      pendingEvaluationHead = pendingEvaluationTail = null;
    }
    if (predicateResult != null
        && (index != 0 || (stringEvaluations == null || stringEvaluations.size() == 0))) finished();
    else if (result.size() == 0
        && predicateResult
            == null) { // when result is empty, there is no need to wait for predicateEvaluation to
                       // finish
      Expression predicate = predicateEvaluation.expression;
      if (predicate.scope() != Scope.DOCUMENT) predicateEvaluation.removeListener(this);
      else event.removeListener(predicate, this);
      finished();
    }
  }
  @Override
  public final void finished(Evaluation evaluation) {
    assert !finished : "can't consume evaluation result after finish";

    if (evaluation == predicateEvaluation) {
      predicateResult = (Boolean) evaluation.getResult();
      assert predicateResult != null : "evaluation result should be non-null";
      if (predicateResult == Boolean.FALSE) {
        if (nodeSetListener != null) {
          for (LongTreeMap.Entry<Object> entry = result.firstEntry();
              entry != null;
              entry = entry.next()) nodeSetListener.discard(entry.getKey());
        }
        result.clear();
        if (stringEvaluations != null) {
          for (Evaluation stringEval : stringEvaluations) stringEval.removeListener(this);
          stringEvaluations = null;
        }
        resultPrepared();
      } else {
        if (predicateChain != -1) decreasePredicateChain();
        if (resultPrepared) finished();
      }
    } else if (evaluation instanceof PredicateEvaluation) {
      PredicateEvaluation predicateEvaluation = (PredicateEvaluation) evaluation;
      remove(predicateEvaluation);

      if (predicateEvaluation.result != null) {
        Object resultItem = predicateEvaluation.result;
        if (resultItem instanceof Evaluation) {
          Evaluation stringEval = (Evaluation) resultItem;
          stringEvaluations.add(stringEval);
          stringEval.addListener(this);
        }
        consumeChildEvaluation(predicateEvaluation.order, resultItem);
      } else {
        if (nodeSetListener != null) nodeSetListener.discard(predicateEvaluation.order);
        consumedResult();
      }
    } else if (evaluation instanceof LocationEvaluation) {
      LocationEvaluation locEval = (LocationEvaluation) evaluation;
      remove(locEval);

      if (locEval.stringEvaluations != null) {
        for (Evaluation stringEval : locEval.stringEvaluations) stringEval.addListener(this);
        stringEvaluations.addAll(locEval.stringEvaluations);
      }
      boolean wasExpired = expired;
      consumeChildEvaluation(locEval.result);
      if (!wasExpired && expired) {
        assert !finished;
        LinkableEvaluation eval = locEval.next;
        while (eval != null) {
          eval.removeListener(this);
          remove(eval);
          eval = eval.next;
        }
        if (pendingEvaluationHead == null) resultPrepared();
      }
    } else {
      stringEvaluations.remove(evaluation);
      consumeChildEvaluation(evaluation.order, evaluation.getResult());
    }
  }
  @Override
  public void onHit(EventID eventID) {
    assert !finished : "getting events even after finish";

    final LocationExpression expression = this.expression;
    if (!lastStep) {
      if (eventID.isEmpty(expression.locationPath.steps[index + 1].axis)) return;
    }

    final Event event = this.event;

    if (positionTracker != null) {
      event.positionTrackerStack.addFirst(positionTracker);
      positionTracker.addEvaluation(event);
    }
    LinkableEvaluation childEval = null;

    Expression predicate = currentStep.predicateSet.getPredicate();
    Object predicateResult = predicate == null ? Boolean.TRUE : event.evaluate(predicate);
    if (predicateResult == Boolean.TRUE) {
      if (lastStep) consume(event);
      else childEval = new LocationEvaluation(expression, index + 1, event, eventID);
    } else if (predicateResult == null) {
      Evaluation predicateEvaluation = event.evaluation;
      if (lastStep) {
        childEval =
            new PredicateEvaluation(
                expression,
                event.order(),
                expression.getResultItem(event),
                event,
                predicate,
                predicateEvaluation);
        if (nodeSetListener != null) nodeSetListener.mayHit();
      } else
        childEval =
            new LocationEvaluation(
                expression, index + 1, event, eventID, predicate, predicateEvaluation);
    }

    if (childEval != null) {
      if (childEval instanceof LocationEvaluation)
        ((LocationEvaluation) childEval).nodeSetListener = nodeSetListener;
      else ((PredicateEvaluation) childEval).nodeSetListener = nodeSetListener;

      childEval.addListener(this);
      if (pendingEvaluationTail != null) {
        pendingEvaluationTail.next = childEval;
        childEval.previous = pendingEvaluationTail;
        pendingEvaluationTail = childEval;
      } else pendingEvaluationHead = pendingEvaluationTail = childEval;
      childEval.start();
    }

    if (positionTracker != null) {
      positionTracker.startEvaluation();
      event.positionTrackerStack.pollFirst();
    }
    if (exactPosition && predicateResult == Boolean.TRUE) {
      manuallyExpired = true;
      expired();
    }
  }