FluentAPI - Usage

As a Java developer I always got the impression that my own code can be made better (unfortunately I am not perfect). That brought me to think about how I could improve the code.

So I thought several times about my code and while looking for example into my unit/integrations tests I found the following code snippet:

1AccessRules rules = new AccessRules();
2
3AccessRule rule1 = new AccessRule("/");
4rule1.add(UserFactory.createInstance("*"), AccessLevel.READ);
5rule1.add(UserFactory.createInstance("harry"), AccessLevel.READ);
6rule1.add(UserFactory.createInstance("brian"), AccessLevel.READ);
7
8rules.add(rule1);

So far so good but it basically violates the DRY rule. If you take a deeper look into it you will recognize that AcessLevel.READ and UserFactory.createInstance(..) are repeated three times as well as rule1.add(...).

Let us change the perspective and move to the user`s point of view. So you can imagine that a user of this code needs to write such kind of code several times which probably is cumbersome.

After a time of thinking about the problem I came across a solution which is from the user`s point of view much more convenient, simpler to read and of course better to understand.

1AccessRules accessRules = new AccessRules.Builder()
2    .forRepository("/")
3    .forUser("*").and("harry").and("brian")
4    .with(AccessLevel.READ)
5    .build();

The only things I had to do was to write some supplemental code to get to the above result. The following code lines are needed for the .forRepository("...") part.

 1public static class Builder {
 2    private AccessRules accessRules;
 3
 4    public Builder() {
 5        this.accessRules = new AccessRules();
 6    }
 7
 8    public AccessRuleBuilder forRepository(String repositoryPath) {
 9        AccessRule accessRule = new AccessRule(repositoryPath);
10        accessRules.add(accessRule);
11        return new AccessRuleBuilder(this, accessRule);
12    }
13
14    public AccessRules build() {
15        return this.accessRules;
16    }
17}

The next step was to get the .forUser("..") working which can be achieved by the following code part:

 1public static class AccessRuleBuilder {
 2    private Builder builder;
 3    private AccessRule rule;
 4
 5    private AccessRuleBuilder(Builder builder, AccessRule rule) {
 6        this.builder = builder;
 7        this.rule = rule;
 8    }
 9
10    public UserBuilder forUser(String userName) {
11        return new UserBuilder(this.builder, rule).and(userName);
12    }
13}

And finally the following code snippet which solves the rest of the things like .and("..") and the last line .with(AccessLeve.READ).

 1public static class UserBuilder {
 2    private List<User> userList;
 3    private AccessRule accessRule;
 4    private Builder builder;
 5
 6    private UserBuilder(Builder builder, AccessRule accessRule) {
 7        this.userList = new ArrayList<User>();
 8        this.builder = builder;
 9        this.accessRule = accessRule;
10    }
11
12    public UserBuilder and(String userName) {
13        this.userList.add(UserFactory.createInstance(userName));
14        return this;
15    }
16
17    public Builder with(AccessLevel level) {
18        for (User user : this.userList) {
19            accessRule.add(user, level);
20        }
21        return this.builder;
22    }
23}