Micro-embedded Java DSL with fluent interface

Here’s a teeny-weeny embedded DSL used to perform password validation. It was created to rationalise existing validation routines as part of a wider piece of work – so it’s perhaps better, but not perfect.

 

Here’s the client code making use of it’s fluent interface:

private static final PasswordValidator SECRET_STRENGTH_VALIDATOR = new PasswordValidator()
  .mustContain().atLeast(SECRET_WORD_MIN).and().noMoreThan(SECRET_WORD_MAX).charactersInTotal()
  .mustContain().atLeast(1).numbers()
  .mustContain().atLeast(1).ofTheseCharacters(ALPHA_CHARS)
  .mustContain().atLeast(1).ofTheseCharacters(SPECIAL_CHARACTERS_STRING)
  .mustOnlyContain().anyOfTheseCharacters(ALPHA_CHARS + SPECIAL_CHARACTERS_STRING).or().numbers().andNothingElse()
  .mustBeMixedCase();



Lets for now ignore the fact that this isn’t injected into the dependant client components – there’s only so much refactoring that one can or should attempt in one sitting.

I like this because it reads pretty much like an English sentence (not that ‘English’ or ‘French’, etc, is what the ‘Language’ stands for in DSL, as an old colleague of mine points out here – but in this particular case I think it’s a nice read-ability bonus), rather than a series of methods which must be read through in their entirety, or worse – a big old uber-method which does all of the validation in one hit, before it’s possible to know exactly what the particular validation rules are.

Of course all of that validation code still exists, but now it’s hidden away in validation rules which are constructed according to however the client code makes use of the possible combinations provided by the DSL, and executed when the validate() method is invoked on the validator. In this way many combinations of validation rules can be applied to different validator instances in a self describing way, using minimal extra code to do so.

So lets see where the complexity has been hidden:

public class PasswordValidator {

  private List rules = new ArrayList();

  public boolean validate(String passwordString) {
    char[] password = passwordString == null ? new char[0] : passwordString.toCharArray();
    boolean result = true;
    for(PasswordValidationRule rule : rules) {
      result = result && rule.execute(password);
    }
    return result;
  }

  public MustContainRule mustContain(){
    MustContainRule rule = new MustContainRule(this);
    rules.add(rule);
    return rule;
  }

  public MustOnlyContainRule mustOnlyContain() {
    MustOnlyContainRule rule = new MustOnlyContainRule(this);
    rules.add(rule);
    return rule;
  }

  public PasswordValidator mustBeMixedCase() {
    rules.add(new PasswordValidationRule() {
      @Override
      public boolean execute(char[] password) {
        String passwordString = new String(password);
        String upper = passwordString.toUpperCase(Locale.ENGLISH);
        String lower = passwordString.toLowerCase(Locale.ENGLISH);
        return (passwordString.equals(lower) | passwordString.equals(upper)) == false;
      }
    });
    return this;
  }

}



Ok. So there isn’t anything too complicated here; just a validator with 3 kinds of validation configurable – mixed case, must have something and must ONLY have something. The mixed case requirement is handled here directly as it is a complete requirement in it’s own right. The other two kinds of requirement are meaningless without some further input (what exactly is it that we must have? Must we have a certain number of that thing? etc). You can see that the rules are constructed to a PasswordValidationRule interface (which specifies the execute() method).

Here’s where those extra details are constructed (I won’t show the must ONLY have option – it’s the same but slightly more simple):

public class MustContainRule implements PasswordValidationRule {
  private String chars = null;
  private boolean numbers = false;
  private int from = 0;
  private int to = 0;
  private MustContainRuleDetailAppender detailAppender;

  MustContainRule(PasswordValidator passwordValidator) {
    this.detailAppender = new MustContainRuleDetailAppender(this, passwordValidator);
  }

  public MustContainRuleDetailAppender atLeast(int num) {
    from = num;
    return detailAppender;
  }

  public MustContainRuleDetailAppender noMoreThan(int num) {
    to = num;
    return detailAppender;
  }

  @Override
  public boolean execute(char[] password) {
    boolean isValid =
         validateParticularCharacterRequirements(password)
      && validateOverallLengthRequirements(password)
      && validateNumberRequirements(password);

    return isValid;
  }

  private boolean validateParticularCharacterRequirements(char[] password) {
    if(chars == null) {
      return true;
    }

    int count = 0;
    for (char passLetter : password) {
      count += StringUtils.countMatches(chars, String.valueOf(passLetter));
    }

    return validateCount(count);
  }

  private boolean validateOverallLengthRequirements(char[] password) {
    if(chars != null) {
      return true;
    }
    return  validateCount(password.length);
  }

  private boolean validateNumberRequirements(char[] password) {
    if(numbers == false) {
      return true;
    }
    int count = 0;
    for (char passLetter : password) {
      if(Character.isDigit(passLetter)) {
        count++;
      }
    }
    return validateCount(count);
  }

  private boolean validateCount(int num) {
    boolean pass = from > 0 ? num >= from : true;
    pass = pass && (to > 0 ? num <= to: true);
    return pass;
  }

  public class MustContainRuleDetailAppender {

    private final MustContainRule mustContainRule;
    private final PasswordValidator passwordValidator;

    private MustContainRuleDetailAppender(MustContainRule mustContainRule, PasswordValidator passwordValidator) {
      this.mustContainRule = mustContainRule;
      this.passwordValidator = passwordValidator;
    }

    public MustContainRule and() {
      return mustContainRule;
    }

    public PasswordValidator ofTheseCharacters(String str) {
      mustContainRule.chars = str;
      return passwordValidator;
    }

    public PasswordValidator charactersInTotal() {
      return passwordValidator;
    }

    public PasswordValidator numbers() {
      mustContainRule.numbers = true;
      return passwordValidator;
    }

  }

}

 

And that’s it.
Here we can see that this more complicated rule makes use of a public inner class – this is required in order to limit the client code to make only valid combinations of validation rule detail, such that the client code is only able to construct a single validation rule, in it’s entirety, at a time. This keeps the validation rule construction code simple (and therefore better tested) and the client code is forced to be written in an easy to understand way. This is achieved by controlling which methods are available depending on which object type is returned from each method, and therefore the methods which are then available in the chain; as such being forced to use the api in a specific way is not a bad thing as it actually limits the client code to invoking only a few sensible options at a time. For example, after

  .mustContain().atLeast(SOME_NUMBER)

the client code only has the option of invoking and() in order to add a numerical limit to that same validation rule currently being constructed, ofTheseCharacters() to specify the character set of which SOME_NUMBER must be used, numbers() to specify that SOME_NUMBER of numbers must be present, or finally charactersInTotal() to indicate that the there must be at least SOME_NUMBER of characters total in the password. Each of these methods, apart from and(), returns the original validator object which means that any further validation criteria must exist as another ValidationRule object. Because the MustContain and MustOnlyContain rules are so tightly coupled with their ….DetailAppender classes they have been implemented as inner classes; they’re effectively part of one larger class, but using this technique it’s possible to simulate a kind of context dependant method accessibility (unless the client code decides to deliberately break the mechanism by not chaining their invocations).

DSL_Methods

Isn’t this more complicated?
In this case I feel like I can justify the marginal one off increase in complexity for the reusable simplicity and readability. All validation will be variations of the same theme – so it feels right to me to standardise the mechanics of those variations.

The DSL apparatus itself is still really pretty simple, and as such it can easily be unit tested with confidence; and with that being the case it feels as though it is actually safer to now implement various validation routines using the DSL’s guiding hand, like this:

private static final PasswordValidator STRENGTH_VALIDATOR_1 = new PasswordValidator()
    .mustContain().atLeast(8).and().noMoreThan(20).charactersInTotal()
    .mustContain().atLeast(2).numbers()
    .mustContain().atLeast(3).ofTheseCharacters(ALPHA_CHARS)
    .mustContain().atLeast(1).ofTheseCharacters(SPECIAL_CHARACTERS)
    .mustOnlyContain().anyOfTheseCharacters(ALPHA_CHARS + SPECIAL_CHARACTERS).or().numbers().andNothingElse()
    .mustBeMixedCase();

private static final PasswordValidator STRENGTH_VALIDATOR_2 = new PasswordValidator()
    .mustContain().atLeast(8).and().noMoreThan(20).charactersInTotal()
    .mustBeMixedCase();

private static final PasswordValidator STRENGTH_VALIDATOR_3 = new PasswordValidator()
    .mustContain().atLeast(10).charactersInTotal()
    .mustContain().noMoreThan(3).numbers()
    .mustOnlyContain().anyOfTheseCharacters(ALPHA_CHARS).or().numbers().andNothingElse();



…than it would be to code them up in the typical, and less structured way. It makes it easier to compare the difference between various validation routines, avoid bugs or assumed behaviour which doesn’t actually exist (which I discovered in the old code during this process, although decent tests should have highlighted that in the first place), and helps to keep that utility class closer to 1 thousand lines than 2 thousand o.O

 

Image | This entry was posted in coding, java, software and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s