Module org.klojang.check


module org.klojang.check

Klojang Check is a Java module dedicated to defensive programming. It lets you specify checks on program input, object state and method arguments in a concise and elegant manner. Here is an example:


 this.numChairs = Check.that(numChairs).is(positive()).is(lte(), 4).is(even()).ok();
 

Documentation

See the User Guide for a detailed description of Defensive Programming using Klojang Check.

IntCheck and ObjectCheck

There are two types of check objects: IntCheck, for validating int values, and ObjectCheck<T>, for validating values of type T. You cannot directly instantiate these classes. You obtain an instance of them through the static factory methods on the Check class. In the example above, the that() static factory method returns an IntCheck instance.

Common Checks

Klojang Check provides a grab bag of common checks on arguments and other types of values in the form of the CommonChecks class. The lte(), positive() and even() checks shown above are static imports from this class. Here are some more examples:


 Check.that(obj, "vehicle").is(instanceOf(), Car.class);
 Check.that(list).isNot(empty());
 Check.that(word).is(keyIn(), dictionary);
 Check.that(dictionary).is(containsKey(), word);
 Check.that(file).is(writable());
 

These checks are associated with predefined error messages, so you don't need to invent them yourself. The first of the above statements, for example, would cause the following error message to be generated if the argument was an instance of Bike:


 vehicle must be instance of Car (was Bike)
 

Predicates and Relations

The checks you pass to the is(...) methods fall apart in two broad categories: Predicate and IntPredicate on the one hand, and Relation, IntRelation and IntObjRelation on the other. The latter are not part of the JDK. They reside inside the Klojang Check module itself. They can be thought of as a "BiPredicate" (which neither exists in the JDK): a function that takes two arguments and returns a boolean. If the two arguments have a certain relationship with each other, the relation is said to exist and the function returns true. Within the context of Klojang Check, the first argument is always the value currently being validated while the second argument is the value that it is to be validated against. In the examples above positive(), even(), empty() and writable() are predicates; lte(), instanceOf(), keyIn() and containsKey() are relations. For more information, see the package description of org.klojang.check.relation.

Handling Validation Errors

When a value fails a test, an error message needs to be generated and an exception needs to be thrown. You have three options here:

  • Klojang Check generates both the exception and the exception message
  • Klojang Check generates the exception and you provide the exception message
  • You do both
The following code snippet provides an example of each of the three variants:

 Check.that(obj, "vehicle").is(instanceOf(), Car.class);
 Check.that(obj).is(instanceOf(), Car.class, "Bikes are not for rent here");
 Check.that(obj).is(instanceOf(), Car.class, () -> new RentalException("Bikes are not for rent here"));
 

Custom Error Messages

If you prefer to emit a custom error message, you can do so by specifying a message pattern and zero or more message arguments. The first message argument can be referenced from within the message pattern as ${0}, the second as ${1}, etc. For example:


 Check.that(word).is(keyIn(), dictionary, "Spelling error. Did you mean: \"${0}\"?", "Pterodactylus");
 

The following message arguments are automatically available within the message pattern:

  1. ${test} The name of the check that was executed, like "lt" or "instanceOf" or "notNull".
  2. ${arg} The value being validated.
  3. ${type} The simple class name of the value.
  4. ${tag} The name of the parameter, field or variable being validated, or, possibly, something more descriptive. If you did not provide a name, ${tag} defaults to "argument".
  5. ${obj} The object of the relationship, in case the check took the form of a Relation or one of its sister interfaces. For example, for the instanceOf() check, ${obj} would be the class that the argument must be an instance of (Car.class in the example above). For checks expressed through a Predicate or IntPredicate, ${obj} will be null.

Here is an example where you don't provide any message arguments yourself, yet still have a dynamically generated error message:


 Check.that(word).is(keyIn(), dictionary, "Missing key \"${arg}\" in ${obj}");
 

Validating Argument Properties

Klojang Check lets you validate argument properties as part of validating the argument.


 this.query = Check.that(query, "query")
  .notHas(Query::offset, "offset", negative())
  .has(Query::limit, "limit", gte(), 10)
  .has(Query::limit, "limit", lt(), 100)
  .ok();
 

This would cause the following error message to be generated if the user specified a limit of 125:


 query.limit must be < 100 (was 125)
 

The CommonProperties class provides some commonly used properties of well-known classes and interfaces, like the size property of a Collection. As with the CommonChecks class, these properties are already associated with a descriptive name of the property they expose. Thus, the error message to be generated requires minimal input from you:


 Check.notNull(emps, "employees").has(size(), gte(), 100);
 

This will cause the following error message to be generated if the size of the emps collection is, say, 42:


 employees.size() must be >= 100 (was 42)
 

Custom Checks

You are not limited to using the checks from the CommonChecks class. You can also define your own checks in the form of lambdas or method references:


 double angle = 45.0;
 Check.that(angle).is(a -> Math.sin(a) > 0, "sine of angle must be positive");
 

Be careful, however, when passing lambdas to the has and notHas methods. These methods are heavily overloaded. Therefore, "vanilla" lambdas (without any type information) may cause the compiler to complain about an Ambiguous method call:


 // WON'T COMPILE! Ambiguous method call
 Check.that(temperature).has(i -> Math.abs(i), i -> i < 30);
 

You can disambiguate this for the compiler by specifying the type of the lambda parameter, or by casting the entire lambda or method reference:


 // specify the type of lambda parameter:
 Check.that(temperature).has(i -> Math.abs(i), (int i) -> i < 30);
 // cast the lambda that extracts the property from the argument:
 Check.that(temperature).has((IntUnaryOperator) i -> Math.abs(i), i -> i < 30);
 // cast the lambda that tests the property:
 Check.that(temperature).has(i -> Math.abs(i), (IntPredicate) i -> i < 30);
 

Throwing a Different Type of Exception

By default, Klojang Check throws an IllegalArgumentException if any of the tests following Check.that(...) fail. To customize this, use the Check.on(...) static factory methods instead. These allow you to change the default exception. The default exception can itself be overridden within the checks themselves. The following example changes the default exception to SQLException, but overrides it for the null check:


 this.query = Check.on(SQLException::new, query, "query")
  .is(notNull(), () -> new NullPointerException())
  .notHas(Query::offset, "offset", negative())
  .has(Query::limit, "limit", gte(), 10)
  .has(Query::limit, "limit", lte(), 10000)
  .ok();
 

Here, too, Klojang Check provides some useful shortcuts through the CommonExceptions class:


 Check.that(word)
  .is(notNull(), npe()) // throw a NullPointerException
  .is(keyIn(), dictionary, illegalState("no such word: \"" + word + "\"");
 

NB when you supply your own exception, you cannot use the ${...} message arguments. You will have to construct the message yourself.

  • Packages

    Exports
    Package
    Description
    The central package of this Java module.
    A small collection of classes and interfaces that do not themselves take part in the validation framework, but are treated as first-class citizens by it.
    A collection of functional interfaces that allow a checked exception to be thrown from their functional method.
    A collection of functional interfaces that together constitute one category of checks that can be executed using Klojang Check.