11
\$\begingroup\$

Over the past year I've quietly developed a library for checking method preconditions. The project is hosted at https://bitbucket.org/cowwoc/preconditions/

I would like to get your feedback on the design and gauge whether this kind of library is of interest to anyone.

Sample usage looks like this:

public class Dog
{
  private final String name;
  private final int friends;

  /**
   * Creates a new Dog.
   *
   * @param name the dog's name
   * @param friends the number of friends the dog has
   * @throws NullPointerException if name is null
   * @throws IllegalArgumentException if name is empty; if friends is outside the range [0, 15]
   */
  public Dog(String name, int friends)
  {
    Preconditions.requireThat(name, "name").isNotNull().isNotEmpty();
    Preconditions.requireThat(friends, "friends").isIn(Range.closed(0, 15));
    this.name = name;
    this.friends = friends;
  }

  /**
   * Causes the dog to bark.
   */
  public void bark()
  {
    System.out.print("Bark! Bark!");
    if (friends <= 2)
      System.out.println("I am lonely. I only have " + friends + " friends!");
    else
      System.out.println("I am one happy dog. I have " + friends + " friends!");
  }
}

Sample output looks like this:

java.lang.NullPointerException: name may not be null
java.lang.IllegalArgumentException: friends (20) must be in the range [0, 15]

When reviewing the code, two classes might be of particular interest:

Preconditions.java:

package org.bitbucket.cowwoc.preconditions;

import java.math.BigDecimal;
import java.net.URI;
import java.nio.file.Path;
import java.time.Year;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * Verifies preconditions of a parameter.
 * <p>
 * This class is not thread-safe.
 * <p>
 * @author Gili Tzabari
 * @param <S> the type of preconditions that was instantiated
 * @param <T> the type of the parameter
 */
public class Preconditions<S extends Preconditions<S, T>, T>
{
    /**
     * Creates new Preconditions.
     * <p>
     * @param <S>       the type of preconditions that was instantiated
     * @param <T>       the type of the parameter
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static <S extends Preconditions<S, T>, T> S requireThat(T parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        @SuppressWarnings("unchecked")
        S self = (S) new Preconditions<>(parameter, name);
        return self;
    }

    /**
     * Creates new CollectionPreconditions.
     * <p>
     * @param <E>       the type of element in the collection
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static <E> CollectionPreconditions<E> requireThat(Collection<E> parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new CollectionPreconditions<>(parameter, name);
    }

    /**
     * Creates new NumberPreconditions.
     * <p>
     * @param <S>       the type of preconditions that was instantiated
     * @param <T>       the type of the number
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static <S extends NumberPreconditions<S, T>, T extends Number & Comparable<? super T>>
        NumberPreconditions<S, T> requireThat(T parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new NumberPreconditions<>(parameter, name);
    }

    /**
     * Creates new BigDecimalPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static BigDecimalPreconditions requireThat(BigDecimal parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new BigDecimalPreconditions(parameter, name);
    }

    /**
     * Creates new MapPreconditions.
     * <p>
     * @param <K>       the type of key in the map
     * @param <V>       the type of value in the map
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static <K, V> MapPreconditions<K, V> requireThat(Map<K, V> parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new MapPreconditions<>(parameter, name);
    }

    /**
     * Creates new PathPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static PathPreconditions requireThat(Path parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new PathPreconditions(parameter, name);
    }

    /**
     * Creates new StringPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static StringPreconditions requireThat(String parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new StringPreconditions(parameter, name);
    }

    /**
     * Creates new UriPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static UriPreconditions requireThat(URI parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new UriPreconditions(parameter, name);
    }

    /**
     * Creates new ClassPreconditions.
     * <p>
     * @param <T>       the type of class
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static <T> ClassPreconditions<T> requireThat(Class<T> parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new ClassPreconditions<>(parameter, name);
    }

    /**
     * Creates new YearPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static YearPreconditions requireThat(Year parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new YearPreconditions(parameter, name);
    }

    /**
     * Creates new OptionalPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @return Preconditions for the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    public static OptionalPreconditions requireThat(Optional<?> parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        return new OptionalPreconditions(parameter, name);
    }
    protected final S self;
    protected T parameter;
    protected final String name;

    /**
     * Creates new Preconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    protected Preconditions(T parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        if (name == null)
            throw new NullPointerException("name may not be null");
        if (name.trim().isEmpty())
            throw new IllegalArgumentException("name may not be empty");
        @SuppressWarnings(
            {
                "unchecked", "LocalVariableHidesMemberVariable"
            })
        S self = (S) this;
        this.self = self;
        this.parameter = parameter;
        this.name = name;
    }

    /**
     * Ensures that the parameter is not null.
     * <p>
     * @return this
     * @throws NullPointerException if parameter is null
     */
    public S isNotNull() throws NullPointerException
    {
        if (parameter == null)
            throw new NullPointerException(name + " may not be null");
        return self;
    }

    /**
     * Ensures that the parameter is not null.
     * <p>
     * @return this
     * @throws IllegalStateException if parameter is null
     */
    public S stateIsNotNull() throws IllegalStateException
    {
        if (parameter == null)
            throw new IllegalStateException(name + " may not be null");
        return self;
    }

    /**
     * Ensures that the parameter is equal to a value.
     * <p>
     * @param value the value to compare to
     * @return this
     * @throws IllegalArgumentException if parameter is not equal to value
     */
    public S isEqualTo(T value) throws IllegalArgumentException
    {
        if (!Objects.equals(parameter, value))
            throw new IllegalArgumentException(name + " must be equal to " + value + ". Was: " + parameter);
        return self;
    }

    /**
     * Ensures that the parameter is an instance of a class.
     * <p>
     * @param type the class to compare to
     * @return this
     * @throws NullPointerException     if {@code parameter} or {@code type} are null
     * @throws IllegalArgumentException if {@code parameter} is not an instance of {@code type}
     */
    public S isInstanceOf(Class<?> type)
        throws NullPointerException, IllegalArgumentException
    {
        if (parameter == null)
            throw new NullPointerException("parameter may not be null");
        if (type == null)
            throw new NullPointerException("type may not be null");
        if (!type.isInstance(parameter))
        {
            throw new IllegalArgumentException(name + " must be an instance of " + type + ". Was: " +
                parameter.getClass());
        }
        return self;
    }
}

NumberPreconditions.java:

package org.bitbucket.cowwoc.preconditions;

import com.google.common.collect.Range;
import java.math.BigDecimal;

/**
 * Verifies preconditions of a {@link Number} parameter.
 * <p>
 * @param <S> the type of preconditions that was instantiated
 * @param <T> the type of the parameter
 * @author Gili Tzabari
 */
public class NumberPreconditions<S extends NumberPreconditions<S, T>, T extends Number & Comparable<? super T>>
    extends Preconditions<S, T>
{
    /**
     * Creates new NumberPreconditions.
     * <p>
     * @param parameter the value of the parameter
     * @param name      the name of the parameter
     * @throws NullPointerException     if name is null
     * @throws IllegalArgumentException if name is empty
     */
    NumberPreconditions(T parameter, String name)
        throws NullPointerException, IllegalArgumentException
    {
        super(parameter, name);
    }

    /**
     * Ensures that the parameter is within range.
     * <p>
     * @param range the range
     * @return this
     * @throws NullPointerException     if range is null
     * @throws IllegalArgumentException if the parameter is not in range
     */
    public S isIn(Range<T> range)
        throws NullPointerException, IllegalArgumentException
    {
        Preconditions.requireThat(range, "range").isNotNull();
        boolean inRange = range.contains(parameter);
        if (inRange)
            return self;
        StringBuilder message = new StringBuilder(name + " (" + parameter + ") must be in the range ");
        switch (range.lowerBoundType())
        {
            case OPEN:
                message.append("(");
                break;
            case CLOSED:
                message.append("[");
                break;
            default:
                throw new AssertionError(range.lowerBoundType().name());
        }
        message.append(range.lowerEndpoint()).append(", ").append(range.upperEndpoint());
        switch (range.lowerBoundType())
        {
            case OPEN:
                message.append(")");
                break;
            case CLOSED:
                message.append("]");
                break;
            default:
                throw new AssertionError(range.lowerBoundType().name());
        }
        throw new IllegalArgumentException(message.toString());
    }

    /**
     * Ensures that the parameter is negative.
     * <p>
     * @return this
     * @throws IllegalArgumentException if the parameter is not negative
     */
    public S isNegative() throws IllegalArgumentException
    {
        if (parameter.longValue() >= 0L)
            throw new IllegalArgumentException(name + " must be negative");
        return self;
    }

    /**
     * Ensures that the parameter is not negative.
     * <p>
     * @return this
     * @throws IllegalArgumentException if the parameter is negative
     */
    public S isNotNegative() throws IllegalArgumentException
    {
        if (parameter.longValue() < 0L)
            throw new IllegalArgumentException(name + " may not be negative");
        return self;
    }

    /**
     * Ensures that the parameter is zero.
     * <p>
     * @return this
     * @throws IllegalArgumentException if the parameter is not zero
     */
    public S isZero() throws IllegalArgumentException
    {
        if (parameter instanceof BigDecimal)
        {
            // Number.longValue() truncates the fractional portion, which we need to take into account
            BigDecimal decimal = (BigDecimal) parameter;
            if (decimal.signum() != 0)
                throw new IllegalArgumentException(name + " must be zero");
            return self;
        }
        if (parameter.longValue() != 0L)
            throw new IllegalArgumentException(name + " must be zero");
        return self;
    }

    /**
     * Ensures that the parameter is not zero.
     * <p>
     * @return this
     * @throws IllegalArgumentException if the parameter is zero
     */
    public S isNotZero() throws IllegalArgumentException
    {
        if (parameter instanceof BigDecimal)
        {
            // Number.longValue() truncates the fractional portion, which we need to take into account
            BigDecimal decimal = (BigDecimal) parameter;
            if (decimal.signum() == 0)
                throw new IllegalArgumentException(name + " may not be zero");
            return self;
        }
        if (parameter.longValue() == 0L)
            throw new IllegalArgumentException(name + " may not be zero");
        return self;
    }

    /**
     * Ensures that the parameter is positive.
     * <p>
     * @return this
     * @throws IllegalArgumentException if the parameter is not positive
     */
    public S isPositive() throws IllegalArgumentException
    {
        if (parameter.longValue() <= 0L)
            throw new IllegalArgumentException(name + " must be positive");
        return self;
    }

    /**
     * Ensures that the parameter is not positive.
     * <p>
     * @return this
     * @throws IllegalArgumentException if the parameter is positive
     */
    public S isNotPositive() throws IllegalArgumentException
    {
        if (parameter.longValue() > 0L)
            throw new IllegalArgumentException(name + " may not be positive");
        return self;
    }

    /**
     * Ensures that the parameter is less than the value of a variable.
     * <p>
     * @param value the value the parameter must be less than
     * @param name  the name of the variable
     * @return this
     * @throws IllegalArgumentException if the {@code parameter} is greater than or equal to
     *                                  {@code value}
     */
    public S isLessThan(T value, String name) throws IllegalArgumentException
    {
        if (parameter.compareTo(value) >= 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be less than " +
                name + " (" + value + ")");
        }
        return self;
    }

    /**
     * Ensures that the parameter is less than a constant.
     * <p>
     * @param value the value the parameter must be less than
     * @return this
     * @throws IllegalArgumentException if the {@code parameter} is greater than or equal to
     *                                  {@code value}
     */
    public S isLessThan(T value) throws IllegalArgumentException
    {
        if (parameter.compareTo(value) >= 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be less than " +
                value);
        }
        return self;
    }

    /**
     * Ensures that the parameter is less than or equal to a variable.
     * <p>
     * @param value the value the parameter must be less than or equal to
     * @param name  the name of the variable
     * @return this
     * @throws IllegalArgumentException if the {@code parameter} is greater than {@code value}
     */
    public S isLessThanOrEqualTo(T value, String name)
        throws IllegalArgumentException
    {
        if (parameter.compareTo(value) > 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be less than " +
                "or equal to " + name + " (" + value + ")");
        }
        return self;
    }

    /**
     * Ensures that the parameter is less than or equal to a constant.
     * <p>
     * @param value the value the parameter must be less than or equal to
     * @return this
     * @throws IllegalArgumentException if the {@code parameter} is greater than {@code value}
     */
    public S isLessThanOrEqualTo(T value)
        throws IllegalArgumentException
    {
        if (parameter.compareTo(value) > 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be less than " +
                "or equal to " + value);
        }
        return self;
    }

    /**
     * Ensures that the parameter is greater than a variable.
     * <p>
     * @param value the value the parameter must be greater than
     * @param name  the name of the variable
     * @return this
     * @throws IllegalArgumentException if the parameter is less than or equal to {@code value}
     */
    public S isGreaterThan(T value, String name)
        throws IllegalArgumentException
    {
        if (parameter.compareTo(value) <= 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be greater than " +
                name + " (" + value + ")");
        }
        return self;
    }

    /**
     * Ensures that the parameter is greater than a constant.
     * <p>
     * @param value the value the parameter must be greater than
     * @return this
     * @throws IllegalArgumentException if the parameter is less than or equal to {@code value}
     */
    public S isGreaterThan(T value)
        throws IllegalArgumentException
    {
        if (parameter.compareTo(value) <= 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be greater than " +
                value);
        }
        return self;
    }

    /**
     * Ensures that the parameter is greater than or bigger than a variable.
     * <p>
     * @param value the value the parameter must be greater than or equal to
     * @param name  the name of the variable
     * @return this
     * @throws IllegalArgumentException if the {@code parameter} is less than to {@code value}
     */
    public S isGreaterThanOrEqualTo(T value, String name)
        throws IllegalArgumentException
    {
        if (parameter.compareTo(value) < 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be greater than " +
                "or equal to " + name + " (" + value + ")");
        }
        return self;
    }

    /**
     * Ensures that the parameter is greater than or bigger than a constant.
     * <p>
     * @param value the value the parameter must be greater than or equal to
     * @return this
     * @throws IllegalArgumentException if the {@code parameter} is less than to {@code value}
     */
    public S isGreaterThanOrEqualTo(T value)
        throws IllegalArgumentException
    {
        if (parameter.compareTo(value) < 0)
        {
            throw new IllegalArgumentException(this.name + " (" + parameter + ") must be greater than " +
                "or equal to " + value);
        }
        return self;
    }
}

Notice that I use self-types in NumberPreconditions so regardless of whether the user is handling an Integer or BigDecimal parameter the builder methods return the correct type.

That's it. Let me know what you think :)

\$\endgroup\$
0

3 Answers 3

10
\$\begingroup\$

public S isIn(Range range)

This method looks strange to me, because of 2 points

  1. It is violating SRP because it is checking if parameter is in range and composing the exception message
  2. The composing of the exception message.

So let us take a look at message composition, which we can extract to a separate method to solve point one also.

private String getExceptionMessage(Range<T> range) {
    StringBuilder message = new StringBuilder(name + " (" + parameter + ") must be in the range ");
    switch (range.lowerBoundType())
    {
        case OPEN:
            message.append("(");
            break;
        case CLOSED:
            message.append("[");
            break;
        default:
            throw new AssertionError(range.lowerBoundType().name());
    }
    message.append(range.lowerEndpoint()).append(", ").append(range.upperEndpoint());
    switch (range.lowerBoundType())
    {
        case OPEN:
            message.append(")");
            break;
        case CLOSED:
            message.append("]");
            break;
        default:
            throw new AssertionError(range.lowerBoundType().name());
    }
    return message.toString();
}  

Here you should check in the second switch the range.upperBoundType()). So the new method should look like

private String getExceptionMessage(Range<T> range) {
    StringBuilder message = new StringBuilder(name + " (" + parameter + ") must be in the range ");
    switch (range.lowerBoundType())
    {
        case OPEN:
            message.append("(");
            break;
        case CLOSED:
            message.append("[");
            break;
        default:
            throw new AssertionError(range.lowerBoundType().name());
    }
    message.append(range.lowerEndpoint()).append(", ").append(range.upperEndpoint());
    switch (range.upperBoundType())
    {
        case OPEN:
            message.append(")");
            break;
        case CLOSED:
            message.append("]");
            break;
        default:
            throw new AssertionError(range.upperBoundType().name());
    }
    return message.toString();
}  

and the former like

public S isIn(Range<T> range)
    throws NullPointerException, IllegalArgumentException
{
    Preconditions.requireThat(range, "range").isNotNull();

    if (range.contains(parameter)) {
        return self;
    }

    throw new IllegalArgumentException(getExceptionMessage(range));
}  

The checking if the parameter is in range can be simplified if you remove the unnecessary boolean variable.

Also it can be seen as a matter of taste, I would like to encourage you to use braces {} for single if statements also.

General

You should be consistent in your coding.

  • either use never braces {} for single if statements or always (which I prefer)
  • In the isIn() method you are checking if the condition the name implies is true (which I prefer) and returning self, where as in the other methods you are checking the opposite of what the name implies and if this is true you are throwing the exception.

    Example:

    S isIn(Range<T> range) -> range.contains(parameter)
    S isNotNegative() -> parameter.longValue() < 0L
    

For composing of small strings, where you need to insert specific values into specific positions, you should consider to use the String.format() method.

Otherwise your shown code looks very good. It is well structured and easy to read.
What I don't like that much is the dependencies on externals like com.google.common.collect.Range. I would like to see the using of these dependencies as an extension on this code.

\$\endgroup\$
1
  • \$\begingroup\$ Your suggestions are excellent and I have applied them to the upcoming release. The only suggestion I could not implement is isolating external dependencies to a separate module/extension. The problem is that, on the one hand, I want NumberPreconditions in core but on the other hand it uses Guava's Range. So I can either replace Range with a core JDK type or accept the dependency in core. I personally prefer the latter. Can you think of a 3rd possibility? \$\endgroup\$
    – Gili
    May 1, 2015 at 17:53
10
\$\begingroup\$

I like that the library provides syntactic sugar to make code easy to read in English. Chaining is a nice design.

IllegalArgumentException is a non-final class, and by subclassing it, you could probably do better than encoding the validation errors into the message string.

I would hope that StringPreconditions.isNotEmpty() also checks that the string is not null. Otherwise, it would be tedious to write .isNotNull().isNotEmpty() everywhere.

My main concerns are about composability and extensibility.

You have a lot of methods to support .isX() and .isNotX(). It seems that there should be a generic way to specify AND / OR / NOT operators. Also, there is no good way to support preconditions other than the ten-or-so types that you have mentioned in the Preconditions class. Nor is there any way to, say, add support for a validator that checks that a string looks like a North American phone number. (StringPreconditions has a regular expression that checks for e-mail addresses — why can't I take advantage of that for a similar goal?)

The library is designed to throw an exception at the first bad parameter it encounters. It would be nice to have a mode that returns a list of all of the validation failures — which parameters failed and why. That would be useful, for example, for validating web forms, where it would provide a better user experience to let the user fix several errors before resubmitting.

I'm not convinced that in its current state, the advantages of using the Preconditions library (mainly syntactic sugar) would be worth the trouble of introducing more external dependencies and dependencies of dependencies. I hope that it could be reworked into a more flexible validation framework, though.

\$\endgroup\$
6
  • \$\begingroup\$ The isNotNull and isNotEmpty dilemma can also be found in other valisldation frameworks. Since I recently worked with the BeanValidation API I find the separation of errors more intuitive. If you wanted a string to be optional, it should be allowed to be null. But iff it's set all other validation rules apply. Failing for null Strings would be an undocumented sideeffect in the isNotEmpty method ... \$\endgroup\$
    – Vogel612
    Nov 10, 2014 at 20:09
  • \$\begingroup\$ @Vogel612 That's an interesting use case, checking that a string is either null or non-empty, but not the empty string "". In that case, .isNotEmpty() should not reject nulls, but should be robust to nulls. \$\endgroup\$ Nov 10, 2014 at 20:13
  • \$\begingroup\$ The main reason isNotEmpty() does not imply isNotNull() is that you should be able to declare multiple constraints per parameter without each constraint checking for null repeatedly. Consider, text.isNotNull().trim().isNotEmpty().isEmailFormat() only checks for null once even though the last 3 conditions assume a non-null value. \$\endgroup\$
    – Gili
    Nov 11, 2014 at 4:30
  • \$\begingroup\$ Good point about being able to negate conditions in a more general fashion, returning a list of all validation errors and splitting Guava into a separate module. I haven't figured out how to model negation as you suggested (nor have I seen other libraries do so) but I'm open to suggestions. \$\endgroup\$
    – Gili
    Nov 11, 2014 at 4:32
  • 1
    \$\begingroup\$ The issue is a general observation that extensibility is not possible. I gave an example of validating phone numbers, but you could imagine social insurance numbers for various countries, Canadian postal codes, etc. Subclassing seems possible, but you wouldn't get the same nice Preconditions.requireThat() syntax. \$\endgroup\$ Nov 11, 2014 at 5:43
1
\$\begingroup\$

As a general comment about design, applicability, and interest, I'd encourage you to build onto an existing standard, like jsr303 (or, less preferably, hamcrest matchers). You could then leverage the existing thinking and implementation that went into it, and provide a value-add of the fluent interface.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Maybe I missed something, but I don't see the practical benefit of doing so. Each one of the technologies you mentioned already have their own (non-fluent interface) design. I don't see how I would integrate the two designs together, especially in the case of JSR303 where everything is driven by annotations. \$\endgroup\$
    – Gili
    Nov 11, 2014 at 4:25
  • \$\begingroup\$ Leaving the annotations aside, you could leverage an existing interface for the Validator implementations, including message construction and more complex composition, as discussed by other posters. You might also get the option (as suggested by @200_success) of a list of errors. It also could get you an immediate library of validators (including compositions, as I said, like allOf and not). \$\endgroup\$
    – schnitz
    Nov 11, 2014 at 9:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.