private void validateLogicalOperator(final OperatorExpression expression, final Logger log) {
    final List<FilterExpression> inputs = expression.getInputs();

    // The "and" operator must have inputs, the "or" operator is allowed without inputs (to allow
    // empty filters):
    if (inputs == null || inputs.size() == 0) {
      if (expression.getOperatorType() != OperatorType.OR) {
        log.report(MessageKey.DATASET_FILTER_LOGICAL_OPERATOR_WITHOUT_INPUTS);
      }
      return;
    }

    // Validate inputs:
    for (final FilterExpression input : inputs) {
      // Input must exist:
      if (input == null) {
        log.report(MessageKey.DATASET_FILTER_LOGICAL_OPERATOR_MISSING_INPUT);
        continue;
      }

      // Input must be of type OperatorExpression:
      if (input == null || !(input instanceof OperatorExpression)) {
        log.report(MessageKey.DATASET_FILTER_LOGICAL_OPERATOR_WRONG_TYPE);
        continue;
      }

      validateOperatorExpression((OperatorExpression) input, log);
    }
  }
  private boolean validateNotNullOperator(final OperatorExpression expression, final Logger log) {
    final List<FilterExpression> inputs = expression.getInputs();

    // A not null operator must have one input:
    if (inputs == null || inputs.size() < 1 || inputs.size() > 2) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_INPUT_COUNT);
      return false;
    }

    // First input must be an instance of AttributeExpression:
    if (inputs.get(0) == null
        || !(inputs.get(0) instanceof AttributeExpression)
        || ((AttributeExpression) inputs.get(0)).getAttributeName() == null
        || ((AttributeExpression) inputs.get(0)).getAttributeType() == null) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_NO_ATTRIBUTE);
      return false;
    }

    // The attribute must exist:
    final AttributeExpression attributeExpression = (AttributeExpression) inputs.get(0);
    final FeatureTypeAttribute attribute =
        findAttribute(
            attributeExpression.getAttributeName(), attributeExpression.getAttributeType());
    if (attribute == null) {
      log.report(
          MessageKey.DATASET_FILTER_OPERATOR_MISSING_ATTRIBUTE,
          attributeExpression.getAttributeName());
      return false;
    }

    return true;
  }
  private void validateInOperator(final OperatorExpression expression, final Logger log) {
    if (!validateOperatorInputs(expression, log)) {
      return;
    }

    final List<FilterExpression> inputs = expression.getInputs();
    final ValueExpression valueExpression = (ValueExpression) inputs.get(1);

    if (valueExpression.getValueType() != ValueType.STRING) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_INCOMPATIBLE_TYPES);
    }
  }
  private void validateOperatorExpression(final OperatorExpression expression, final Logger log) {
    // Expression must have an operator:
    final OperatorType operatorType = expression.getOperatorType();
    if (operatorType == null) {
      log.report(
          MessageKey.DATASET_FILTER_TECHNICAL_ERROR,
          "An operator expression must have an operator type.");
      return;
    }

    // Operator specific validation:
    switch (operatorType) {

        // Logical operators:
      case AND:
      case OR:
        validateLogicalOperator(expression, log);
        break;

        // Comparison operators:
      case EQUALS:
      case GREATER_THAN:
      case GREATER_THAN_EQUAL:
      case LESS_THAN:
      case LESS_THAN_EQUAL:
      case NOT_EQUALS:
        validateComparisonOperator(expression, log);
        break;

        // In-operator:
      case IN:
        validateInOperator(expression, log);
        break;

        // Like-operator:
      case LIKE:
        validateLikeOperator(expression, log);
        break;

      case NOT_NULL:
        validateNotNullOperator(expression, log);
        break;

      default:
        log.report(
            MessageKey.DATASET_FILTER_TECHNICAL_ERROR,
            String.format("Unknown operator type `%s`", operatorType));
        break;
    }
  }
  private void validateComparisonOperator(final OperatorExpression expression, final Logger log) {
    if (!validateOperatorInputs(expression, log)) {
      return;
    }

    final List<FilterExpression> inputs = expression.getInputs();
    final AttributeExpression attributeExpression = (AttributeExpression) inputs.get(0);
    final FeatureTypeAttribute attribute =
        findAttribute(
            attributeExpression.getAttributeName(), attributeExpression.getAttributeType());

    // The attribute and value must have matching types:
    if (!compareAttributeType(attribute, ((ValueExpression) inputs.get(1)).getValueType())) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_INCOMPATIBLE_TYPES);
    }
  }
  private boolean validateOperatorInputs(final OperatorExpression expression, final Logger log) {
    final List<FilterExpression> inputs = expression.getInputs();

    // A comparison operator must have exactly two inputs:
    if (inputs == null || inputs.size() != 2) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_INPUT_COUNT);
      return false;
    }

    // First input must be an instance of AttributeExpression:
    boolean invalidInputs = false;
    if (inputs.get(0) == null
        || !(inputs.get(0) instanceof AttributeExpression)
        || ((AttributeExpression) inputs.get(0)).getAttributeName() == null
        || ((AttributeExpression) inputs.get(0)).getAttributeType() == null) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_NO_ATTRIBUTE);
      invalidInputs = true;
    }

    // Second input must be an instance of ValueExpression:
    if (inputs.get(1) == null
        || !(inputs.get(1) instanceof ValueExpression)
        || ((ValueExpression) inputs.get(1)).getStringValue() == null
        || ((ValueExpression) inputs.get(1)).getValueType() == null) {
      log.report(MessageKey.DATASET_FILTER_OPERATOR_NO_VALUE);
      invalidInputs = true;
    }

    if (invalidInputs) {
      return false;
    }

    // The attribute must exist:
    final AttributeExpression attributeExpression = (AttributeExpression) inputs.get(0);
    final FeatureTypeAttribute attribute =
        findAttribute(
            attributeExpression.getAttributeName(), attributeExpression.getAttributeType());
    if (attribute == null) {
      log.report(
          MessageKey.DATASET_FILTER_OPERATOR_MISSING_ATTRIBUTE,
          attributeExpression.getAttributeName());
    }

    return true;
  }