/**
   * validates the number value using the range constraint provided
   *
   * @param result - a holder for any already run validation results
   * @param value - the value to validate
   * @param constraint - the range constraint to use
   * @param attributeValueReader - provides access to the attribute being validated
   * @return the passed in result, updated with the results of the processing
   * @throws IllegalArgumentException
   */
  protected ConstraintValidationResult validateRange(
      DictionaryValidationResult result,
      Number value,
      RangeConstraint constraint,
      AttributeValueReader attributeValueReader)
      throws IllegalArgumentException {

    // TODO: JLR - need a code review of the conversions below to make sure this is the best way to
    // ensure accuracy across all numerics
    // This will throw NumberFormatException if the value is 'NaN' or infinity... probably shouldn't
    // be a NFE but something more intelligible at a higher level
    BigDecimal number = value != null ? new BigDecimal(value.toString()) : null;

    String inclusiveMaxText = constraint.getInclusiveMax();
    String exclusiveMinText = constraint.getExclusiveMin();

    BigDecimal inclusiveMax = inclusiveMaxText != null ? new BigDecimal(inclusiveMaxText) : null;
    BigDecimal exclusiveMin = exclusiveMinText != null ? new BigDecimal(exclusiveMinText) : null;

    return isInRange(
        result,
        number,
        inclusiveMax,
        inclusiveMaxText,
        exclusiveMin,
        exclusiveMinText,
        attributeValueReader);
  }
  /**
   * validates the date value using the range constraint provided
   *
   * @param result - a holder for any already run validation results
   * @param value - the value to validate
   * @param constraint - the range constraint to use
   * @param attributeValueReader - provides access to the attribute being validated
   * @return the passed in result, updated with the results of the processing
   * @throws IllegalArgumentException
   */
  protected ConstraintValidationResult validateRange(
      DictionaryValidationResult result,
      Date value,
      RangeConstraint constraint,
      AttributeValueReader attributeValueReader)
      throws IllegalArgumentException {

    Date date = value != null ? ValidationUtils.getDate(value, dateTimeService) : null;

    String inclusiveMaxText = constraint.getInclusiveMax();
    String exclusiveMinText = constraint.getExclusiveMin();

    Date inclusiveMax =
        inclusiveMaxText != null
            ? ValidationUtils.getDate(inclusiveMaxText, dateTimeService)
            : null;
    Date exclusiveMin =
        exclusiveMinText != null
            ? ValidationUtils.getDate(exclusiveMinText, dateTimeService)
            : null;

    return isInRange(
        result,
        date,
        inclusiveMax,
        inclusiveMaxText,
        exclusiveMin,
        exclusiveMinText,
        attributeValueReader);
  }
  /**
   * validates the value provided using {@code RangeConstraint}
   *
   * @param result - a holder for any already run validation results
   * @param value - the value to validate
   * @param constraint - the range constraint to use
   * @param attributeValueReader - provides access to the attribute being validated
   * @return the passed in result, updated with the results of the processing
   * @throws AttributeValidationException if validation fails
   */
  protected ConstraintValidationResult processSingleRangeConstraint(
      DictionaryValidationResult result,
      Object value,
      RangeConstraint constraint,
      AttributeValueReader attributeValueReader)
      throws AttributeValidationException {
    // Can't process any range constraints on null values
    if (ValidationUtils.isNullOrEmpty(value)
        || (constraint.getExclusiveMin() == null && constraint.getInclusiveMax() == null)) {
      return result.addSkipped(attributeValueReader, CONSTRAINT_NAME);
    }

    // This is necessary because sometimes we'll be getting a string, for example, that represents a
    // date.
    DataType dataType = constraint.getDataType();
    Object typedValue = value;

    if (dataType != null) {
      typedValue = ValidationUtils.convertToDataType(value, dataType, dateTimeService);
    } else if (value instanceof String) {
      // assume string is a number of type double
      try {
        Double d = Double.parseDouble((String) value);
        typedValue = d;
      } catch (NumberFormatException n) {
        // do nothing, typedValue is never reset
      }
    }

    // TODO: decide if there is any reason why the following would be insufficient - i.e. if
    // something numeric could still be cast to String at this point
    if (typedValue instanceof Date) {
      return validateRange(result, (Date) typedValue, constraint, attributeValueReader);
    } else if (typedValue instanceof Number) {
      return validateRange(result, (Number) typedValue, constraint, attributeValueReader);
    }

    return result.addSkipped(attributeValueReader, CONSTRAINT_NAME);
  }