protected ConstraintValidationResult validateLength(
      DictionaryValidationResult result,
      String value,
      LengthConstraint constraint,
      AttributeValueReader attributeValueReader)
      throws IllegalArgumentException {
    Integer valueLength = Integer.valueOf(value.length());

    Integer maxLength = constraint.getMaxLength();
    Integer minLength = constraint.getMinLength();

    Result lessThanMax = ValidationUtils.isLessThanOrEqual(valueLength, maxLength);
    Result greaterThanMin = ValidationUtils.isGreaterThanOrEqual(valueLength, minLength);

    // It's okay for one end of the range to be undefined - that's not an error. It's only an error
    // if one of them is invalid
    if (lessThanMax != Result.INVALID && greaterThanMin != Result.INVALID) {
      // Of course, if they're both undefined then we didn't actually have a real constraint
      if (lessThanMax == Result.UNDEFINED && greaterThanMin == Result.UNDEFINED) {
        return result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME);
      }

      // In this case, we've succeeded
      return result.addSuccess(attributeValueReader, CONSTRAINT_NAME);
    }

    String maxErrorParameter = maxLength != null ? maxLength.toString() : null;
    String minErrorParameter = minLength != null ? minLength.toString() : null;

    // If both comparisons happened then if either comparison failed we can show the end user the
    // expected range on both sides.
    if (lessThanMax != Result.UNDEFINED && greaterThanMin != Result.UNDEFINED) {
      return result.addError(
          RANGE_KEY,
          attributeValueReader,
          CONSTRAINT_NAME,
          RiceKeyConstants.ERROR_OUT_OF_RANGE,
          minErrorParameter,
          maxErrorParameter);
    }
    // If it's the max comparison that fails, then just tell the end user what the max can be
    else if (lessThanMax == Result.INVALID) {
      return result.addError(
          MAX_LENGTH_KEY,
          attributeValueReader,
          CONSTRAINT_NAME,
          RiceKeyConstants.ERROR_INCLUSIVE_MAX,
          maxErrorParameter);
    }
    // Otherwise, just tell them what the min can be
    else {
      return result.addError(
          MIN_LENGTH_KEY,
          attributeValueReader,
          CONSTRAINT_NAME,
          RiceKeyConstants.ERROR_EXCLUSIVE_MIN,
          minErrorParameter);
    }
  }
  /**
   * checks whether the value provided is in the range specified by inclusiveMax and exclusiveMin
   *
   * @param result a holder for any already run validation results
   * @param value the value to check
   * @param inclusiveMax the maximum value of the attribute
   * @param inclusiveMaxText the string representation of inclusiveMax
   * @param exclusiveMin the minimum value of the attribute
   * @param exclusiveMinText the string representation of exclusiveMin
   * @param attributeValueReader provides access to the attribute being validated
   * @return the passed in result, updated with the results of the range check
   */
  private <T> ConstraintValidationResult isInRange(
      DictionaryValidationResult result,
      T value,
      Comparable<T> inclusiveMax,
      String inclusiveMaxText,
      Comparable<T> exclusiveMin,
      String exclusiveMinText,
      AttributeValueReader attributeValueReader) {
    // What we want to know is that the maximum value is greater than or equal to the number passed
    // (the number can be equal to the max, i.e. it's 'inclusive')
    Result lessThanMax = ValidationUtils.isLessThanOrEqual(value, inclusiveMax);
    // On the other hand, since the minimum is exclusive, we just want to make sure it's less than
    // the number (the number can't be equal to the min, i.e. it's 'exclusive')
    Result greaterThanMin = ValidationUtils.isGreaterThan(value, exclusiveMin);

    // It's okay for one end of the range to be undefined - that's not an error. It's only an error
    // if one of them is actually invalid.
    if (lessThanMax != Result.INVALID && greaterThanMin != Result.INVALID) {
      // Of course, if they're both undefined then we didn't actually have a real constraint
      if (lessThanMax == Result.UNDEFINED && greaterThanMin == Result.UNDEFINED) {
        return result.addNoConstraint(attributeValueReader, CONSTRAINT_NAME);
      }

      // In this case, we've succeeded
      return result.addSuccess(attributeValueReader, CONSTRAINT_NAME);
    }

    // If both comparisons happened then if either comparison failed we can show the end user the
    // expected range on both sides.
    if (lessThanMax != Result.UNDEFINED && greaterThanMin != Result.UNDEFINED) {
      return result.addError(
          RANGE_KEY,
          attributeValueReader,
          CONSTRAINT_NAME,
          RiceKeyConstants.ERROR_OUT_OF_RANGE,
          exclusiveMinText,
          inclusiveMaxText);
    }
    // If it's the max comparison that fails, then just tell the end user what the max can be
    else if (lessThanMax == Result.INVALID) {
      return result.addError(
          MAX_INCLUSIVE_KEY,
          attributeValueReader,
          CONSTRAINT_NAME,
          RiceKeyConstants.ERROR_INCLUSIVE_MAX,
          inclusiveMaxText);
    }
    // Otherwise, just tell them what the min can be
    else {
      return result.addError(
          MIN_EXCLUSIVE_KEY,
          attributeValueReader,
          CONSTRAINT_NAME,
          RiceKeyConstants.ERROR_EXCLUSIVE_MIN,
          exclusiveMinText);
    }
  }