Skip to content

Commit

Permalink
Add abstraction for a CoffeeNet view model
Browse files Browse the repository at this point in the history
Multiple parts of CoffeeNet contribute UI/UX components to the final
application. This abstraction allows to write these in a
technology-agnostic way.
  • Loading branch information
punycode committed Feb 17, 2020
1 parent 86b939e commit 0be5467
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 0 deletions.
6 changes: 6 additions & 0 deletions coffeenet-platform-domain/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@
</description>

<dependencies>
<!-- Runtime dependencies -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package rocks.coffeenet.platform.domain.web;

import com.fasterxml.jackson.annotation.JsonAnyGetter;

import java.util.AbstractMap;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;


/**
* This view model makes common attributes available to the view layer in web applications. It is made available under
* the name {@link CoffeeNetModel#MODEL_NAME}.
*
* <p>Each attribute element can be singular or a hierarchical object like a POJO or nested Map.</p>
*
* @author Florian 'punycode' Krupicka - [email protected]
*/
public class CoffeeNetModel extends AbstractMap<String, Object> {

public static final String MODEL_NAME = "coffeenet";

private final Map<String, Object> attributes;

private CoffeeNetModel(Builder builder) {

this.attributes = Collections.unmodifiableMap(builder.content);
}

@Override
public Set<Entry<String, Object>> entrySet() {

return attributes.entrySet();
}


@JsonAnyGetter
Map<String, Object> getAttributes() {

return attributes;
}


@Override
public boolean equals(Object o) {

if (this == o) {
return true;
}

if (o == null || getClass() != o.getClass() || !super.equals(o)) {
return false;
}

return Objects.equals(attributes, ((CoffeeNetModel) o).attributes);
}


@Override
public int hashCode() {

return Objects.hash(super.hashCode(), attributes);
}

/**
* Builder for creating immutable {@link CoffeeNetModel} instances.
*/
public static class Builder {

private final Map<String, Object> content;

public Builder() {

this.content = new LinkedHashMap<>();
}

/**
* Contribute a view model property using given {@code key} and {@code value}.
*/
public Builder withDetail(String key, Object value) {

this.content.put(key, value);

return this;
}


/**
* Contribute multiple view model properties given the contents of {@code details}.
*/
public Builder withDetails(Map<String, Object> details) {

this.content.putAll(details);

return this;
}


/**
* Create a new {@link CoffeeNetModel} instance based on the state of this builder.
*/
public CoffeeNetModel build() {

return new CoffeeNetModel(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package rocks.coffeenet.platform.domain.web;

/**
* Contributes additional properties to the global COLA view model.
*
* @author Florian 'punycode' Krupicka - [email protected]
*/
@FunctionalInterface
public interface CoffeeNetModelContributor {

/**
* Contribute parameters.
*
* @param builder
*/
void contribute(CoffeeNetModel.Builder builder);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package rocks.coffeenet.platform.domain.web;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;


/**
* @author Florian 'punycode' Krupicka - [email protected]
*/
class CoffeeNetModelContributorTests {

private CoffeeNetModel.Builder builder;

@BeforeEach
void setup() {

builder = new CoffeeNetModel.Builder();
}


@Test
@DisplayName("should add singular detail from contributer")
void singularDetail() {

CoffeeNetModelContributor c = (b) -> b.withDetail("single", "value");

c.contribute(builder);

CoffeeNetModel model = builder.build();
assertThat(model).containsExactly(entry("single", "value"));
}


@Test
@DisplayName("should add complex detail from contributer")
void complexDetail() {

Map<String, Object> complex = Collections.singletonMap("key", "value");
CoffeeNetModelContributor c = (b) -> b.withDetail("complex", complex);

c.contribute(builder);

CoffeeNetModel model = builder.build();
assertThat(model).contains(entry("complex", complex));
}


@Test
@DisplayName("should add details from multiple contributers")
void multipleContributors() {

CoffeeNetModelContributor c1 = (b) -> b.withDetail("key1", "value1");
CoffeeNetModelContributor c2 = (b) -> b.withDetail("key2", "value2");

c1.contribute(builder);
c2.contribute(builder);

CoffeeNetModel model = builder.build();
assertThat(model).contains(entry("key2", "value2"), entry("key1", "value1"));
}


@Test
@DisplayName("should add multiple details from contributer")
void multipleDetails() {

HashMap<String, Object> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");

CoffeeNetModelContributor c = (b) -> b.withDetails(map);

c.contribute(builder);

CoffeeNetModel model = builder.build();
assertThat(model).containsExactly(entry("key1", "value1"), entry("key2", "value2"));
}
}

0 comments on commit 0be5467

Please sign in to comment.