Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Generics in Builder

How can I use generics propery in my particular case? The code first, then the explanation:

AbstractConstraint.java

public abstract class AbstractConstraint {
    public abstract Constraint[] getConstraints();
}

AccountConstraint.java

public class AccountConstraint extends AbstractConstraint {
    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;
    }

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

    public static class Builder extends ConstraintBuilder<AccountConstraint> {
        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;
            init();
        }

        public Builder accountId(final int val) {
            this.accountIdConstraint = new Constraint<>(operation, truthed, new Range<>(val), "accountId");
            return this;
        }

        public Builder accountId(final int min, final int max) {
            this.accountIdConstraint = new Constraint<>(operation, truthed, new Range<>(min, max), "accountId");
            return this;
        }

        public Builder accountId(final Range<Integer> accountId) {
            this.accountIdConstraint = new Constraint<>(operation, truthed, accountId, "accountId");
            return this;
        }

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

        public Builder email(final String email) {
            this.emailConstraint = new Constraint<>(operation, truthed, email, "email");
            return this;
        }

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

ConstraintBuilder.java

public abstract class ConstraintBuilder<T> {
    protected boolean truthed;
    protected Operation operation;

    protected void init() {
        truthed = true;
        operation = Operation.IS;
    }

    public ConstraintBuilder not() {
        truthed = false;
        return this;
    }

    public ConstraintBuilder like() {
        operation = Operation.LIKE;
        return this;
    }

    public abstract T build();
}

I want to be able to call new AccountConstraint.Builder().not().username("test"); but this is not possible as I lose the 'reference to the builder' at new AccountConstraint.Builder().not()., ie. I cannot select username("test") anymore.

In what ways could I fix this? I do want that the AccountBuilder.Builder extends ConstraintBuilder<AccountConstraint.Builder> such that I do not have to duplicate the commonly shared methods then.

Regards.


EDIT: I managed to get it working:

See the answer below for the changes.

I hope I haven't broken any Java fundamentals with this solution, I hope it is more of a solution than a dirty hack. I would be pleased if someone could review this edit.

like image 469
skiwi Avatar asked Aug 24 '26 01:08

skiwi


1 Answers

I think this should work:

Builder builder = (Builder) new AccountConstraint.Builder().not();
builder = builder.username("test");

Your issue is that:

new AccountConstraint.Builder().not()

returns a ConstrainBuilder<T>, which doesn't necessarily have access to username(final String). So, you cast it to a Builder builder, and then call username(final String) on builder.

EDIT: You can turn this into one line:

   ((Builder) (new AccountConstraint.Builder().not())).username("test");

EDIT 2: You could override not() in Builder: make it call super.not() and cast the return to a Builder. As in:

public Builder not() 
{
    return (Builder) super.not();
}
like image 178
Steve P. Avatar answered Aug 25 '26 17:08

Steve P.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!