1

currently I have got this class which implements the Builder pattern, for sake of readibility I have chosen to omit some methods, more precisely I only show the build methods of username.

package dao.constraint;

import java.util.Arrays;

public class AccountConstraint {
    private Constraint<Range<Integer>> accountIdConstraint;
    private Constraint<String> usernameConstraint;
    private Constraint<String> passwordConstraint;
    private Constraint<String> emailConstraint;

    private AccountConstraint(Builder builder) {
        this.accountIdConstraint = builder.accountIdConstraint;
        this.usernameConstraint = builder.usernameConstraint;
        this.passwordConstraint = builder.passwordConstraint;
        this.emailConstraint = builder.emailConstraint;
    }

    public Constraint<Range<Integer>> getAccountIdConstraint() {
        return accountIdConstraint;
    }

    public Constraint<String> getUsernameConstraint() {
        return usernameConstraint;
    }

    public Constraint<String> getPasswordConstraint() {
        return passwordConstraint;
    }

    public Constraint<String> getEmailConstraint() {
        return emailConstraint;
    }

    public Constraint[] getConstraints() {
        return Arrays.asList(this.getAccountIdConstraint(), this.getUsernameConstraint(), this.getPasswordConstraint(), this.getEmailConstraint()).toArray(new Constraint[4]);
    }

    public static class Builder {
        private Constraint<Range<Integer>> accountIdConstraint;
        private Constraint<String> usernameConstraint;
        private Constraint<String> passwordConstraint;
        private Constraint<String> emailConstraint;

        public Builder() {
            this.accountIdConstraint = null;
            this.usernameConstraint = null;
            this.passwordConstraint = null;
            this.emailConstraint = null;
        }

        public Builder username(final String username) {
            this.usernameConstraint = new Constraint<>(Operation.IS, true, username, "username");
            return this;
        }

        public Builder notUsername(final String username) {
            this.usernameConstraint = new Constraint<>(Operation.IS, false, username, "username");
            return this;
        }

        public Builder usernameLike(final String username) {
            this.usernameConstraint = new Constraint<>(Operation.LIKE, true, username, "username");
            return this;
        }

        public Builder usernameNotLike(final String username) {
            this.usernameConstraint = new Constraint<>(Operation.LIKE, false, username, "username");
            return this;
        }

        public AccountConstraint build() {
            return new AccountConstraint(this);
        }
    }
}

As you can see there is very subtle difference between AccountConstraint.Builder.username(String s) and AccountConstraint.Builder.notUsername(String s).

I would like to be able to write something like new AccountConstraint.Builder().not(username(s));. However as I know this is not valid Java syntax if username(String s) is not defined in the calling Java class. I neither wish to repeat the whole AccountConstraint.Builder() again to reach the username(String s) part. Any solutions?

Second question: Can AccountConstraint.getConstraints() be improved or written more simple?

Regards.

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
skiwi
  • 66,971
  • 31
  • 131
  • 216

3 Answers3

2

What I find extremely elegant in this situations is to write a utility class with static factory methods like.

public static Constraint userName(...) { ... }

and to import static blabla.Utility.username;

Then you can write almost declarative human-readable queries in java. This is very much as for the hamcrest library for unit testing where you write something like.

Assert.assertThat(blabla, is(equalTo(nullValue()));

In this case Not should implement Constraint and just negates the nested (referenced) constraint like this:

public static Constraint not(Constraint negated) { return new Not(negated); }

this results in code like

PreparedStatement ps = new QueryBuilder()
 .select()
 .from(table("accounts")
 .where(not(username(equalTo("blabla")))
 .compile();

You can add static factories for boolean combinations:

.where(and(
  .not(...),
  .not(or(...))

Defining constraints like this (static factory methods as opposed to adding them to the builder) thus makes them easily composable.

avidD
  • 441
  • 1
  • 6
  • 16
2

you could make not a method of your builder, setting a flag, which then negates the next constraint.

private boolean negate = false;

public Builder not() {
    negate = true;
}

public Builder username(final String username) {
    this.usernameConstraint = new Constraint<>(Operation.IS, !negate, username, "username");
    negate = false;
    return this;
}
Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
2

For your second question:

   public Constraint[] getConstraints() {
        return Arrays.asList(this.getAccountIdConstraint(), 
            this.getUsernameConstraint(),
            this.getPasswordConstraint(),
            this.getEmailConstraint())
            .toArray(new Constraint[4]);
    }

can be re-written to :

   public Constraint[] getConstraints() {
        return new Constraint[] {
            this.accountIdConstraint,
            this.usernameConstraint,
            this.passwordConstraint,
            this.emailConstraint
        };
    }

But IMO, returning a List or Set would be better than an array.

Genzer
  • 2,921
  • 2
  • 25
  • 38
  • The point is that I got another method accepting a varargs of Constraints, ie. `Constraint... constraints` and I would like to make it accept that. But your suggestion makes it nicer already :) – skiwi Jun 29 '13 at 14:04