/**
   * Evaluates this expression and all its children.
   *
   * @param depth of this expression in expression tree (root's depth = 0)
   * @return result of evaluation
   * @see #evaluate(ProcessingContext)
   */
  protected Value evaluate(final ProcessingContext context, final int depth) {
    Value result;
    final boolean thisIsValid = context.isSubjectValid() || isThisExpressionValid(context);
    if (thisIsValid) {
      /* Expression is valid, so evaluate it */
      result = evaluateValidSelfAndChildren(context, depth);
    } else {
      /* Expression is not valid, so register a warning and return NULL */
      context.fireRuntimeWarning(
          this, "Expression is not valid and will not be evaluated. Returning NULL instead");
      result = NullValue.INSTANCE;
    }

    /* Log result of evaluation. */
    if (logger.isTraceEnabled() || logger.isDebugEnabled()) {
      final String format = "{}{} -> {}({})";
      final Object[] arguments =
          new Object[] {
            formatIndent(depth), getClass().getSimpleName(), result.getBaseType(), result
          };
      if (!(getParent() instanceof Expression)) {
        logger.debug(format, arguments);
      } else {
        logger.trace(format, arguments);
      }
    }
    return result;
  }
 /** Returns true if any subexpression is NULL; false otherwise. */
 protected static boolean isAnyChildNull(final Value[] childValues) {
   for (final Value childValue : childValues) {
     if (childValue.isNull()) {
       return true;
     }
   }
   return false;
 }
  @Override
  public boolean evaluate(final ItemProcessingContext context) throws TemplateProcessingInterrupt {
    final Value value = getExpression().evaluate(context);

    if (value.isNull() || !((BooleanValue) value).booleanValue()) {
      return false;
    }

    super.evaluate(context);
    return true;
  }
  @Override
  public boolean validateResponse(
      final ItemSessionController itemSessionController, final Value responseValue) {
    /* Extract response values */
    final Set<Identifier> responseHottextIdentifiers = new HashSet<Identifier>();
    if (responseValue.isNull()) {
      /* (Empty response) */
    } else if (responseValue.getCardinality().isList()) {
      /* (Container response) */
      for (final SingleValue value : (ListValue) responseValue) {
        responseHottextIdentifiers.add(((IdentifierValue) value).identifierValue());
      }
    } else {
      /* (Single response) */
      responseHottextIdentifiers.add(((IdentifierValue) responseValue).identifierValue());
    }

    /* Check the number of responses */
    final int minChoices = getMinChoices();
    final int maxChoices = getMaxChoices();
    if (responseHottextIdentifiers.size() < minChoices) {
      return false;
    }
    if (maxChoices != 0 && responseHottextIdentifiers.size() > maxChoices) {
      return false;
    }

    /* Make sure each choice is a valid identifier */
    final Set<Identifier> hottextIdentifiers = new HashSet<Identifier>();
    final List<Hottext> hottexts = QueryUtils.search(Hottext.class, this);
    for (final Hottext hottext : hottexts) {
      hottextIdentifiers.add(hottext.getIdentifier());
    }
    for (final Identifier responseHottextIdentifier : responseHottextIdentifiers) {
      if (!hottextIdentifiers.contains(responseHottextIdentifier)) {
        return false;
      }
    }

    return true;
  }
  @Override
  public boolean validateResponse(
      final ItemSessionController itemSessionController, final Value responseValue) {
    /* Extract response values */
    final Set<Identifier> responseChoiceIdentifiers = new HashSet<Identifier>();
    if (responseValue.isNull()) {
      /* (Empty response) */
    } else if (responseValue.getCardinality().isList()) {
      /* (Container response) */
      for (final SingleValue hotspotChoiceIdentifier : (ListValue) responseValue) {
        responseChoiceIdentifiers.add(
            ((IdentifierValue) hotspotChoiceIdentifier).identifierValue());
      }
    } else {
      /* (Single response - this won't actually happen) */
      responseChoiceIdentifiers.add(((IdentifierValue) responseValue).identifierValue());
    }

    /* Chheck min/max (if set) */
    final Integer maxChoices = getMaxChoices();
    final Integer minChoices = getMinChoices();
    if (maxChoices != null && minChoices != null) {
      if (responseChoiceIdentifiers.size() < minChoices.intValue()
          || responseChoiceIdentifiers.size() > maxChoices.intValue()) {
        return false;
      }
    }

    /* Check that each identifier is valid */
    final Set<Identifier> choiceIdentifiers = new HashSet<Identifier>();
    for (final HotspotChoice choice : getHotspotChoices()) {
      choiceIdentifiers.add(choice.getIdentifier());
    }
    for (final Identifier choiceIdentifier : responseChoiceIdentifiers) {
      if (!choiceIdentifiers.contains(choiceIdentifier)) {
        return false;
      }
    }

    return true;
  }
 private String stringifyQtiValue(final Value value) {
   if (qtiWorksDeploymentSettings.isEnableMathAssessExtension()
       && GlueValueBinder.isMathsContentRecord(value)) {
     /* This is a special MathAssess "Maths Content" variable. In this case, we'll record
      * just the ASCIIMath input form or the Maxima form, if either are available.
      */
     final RecordValue mathsValue = (RecordValue) value;
     final SingleValue asciiMathInput =
         mathsValue.get(MathAssessConstants.FIELD_CANDIDATE_INPUT_IDENTIFIER);
     if (asciiMathInput != null) {
       return "ASCIIMath[" + asciiMathInput.toQtiString() + "]";
     }
     final SingleValue maximaForm = mathsValue.get(MathAssessConstants.FIELD_MAXIMA_IDENTIFIER);
     if (maximaForm != null) {
       return "Maxima[" + maximaForm.toQtiString() + "]";
     }
   }
   /* Just convert to QTI string in the usual way */
   return value.toQtiString();
 }