package fr.devoxx.sentimental.livingdocumentation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.livingdocumentation.dotdiagram.DotStyles.ASSOCIATION_EDGE_STYLE;
import static org.livingdocumentation.dotdiagram.DotStyles.IMPLEMENTS_EDGE_STYLE;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Properties;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.junit.Test;
import org.livingdocumentation.dotdiagram.DotGraph;
import org.livingdocumentation.dotdiagram.DotGraph.Cluster;
import org.livingdocumentation.dotdiagram.DotGraph.Digraph;
import org.livingdocumentation.dotdiagram.DotWriter;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.google.common.reflect.ClassPath.ClassInfo;
/**
* Living Diagram of the Hexagonal Architecture generated out of the code thanks
* to the package naming conventions.
*/
public class ArchitectureDiagramTest {
private final DotGraph graph = new DotGraph("Hexagonal Architecture", "LR");
@Test
public void generateDiagram() throws Exception {
final ImmutableSet<ClassInfo> allClasses = ClassPath.from(Thread.currentThread().getContextClassLoader())
.getTopLevelClasses();
final Digraph digraph = graph.getDigraph();
digraph.setOptions("rankdir=LR");
final String prefix = "fr.devoxx.sentimental.";
Stream<ClassInfo> domain = allClasses.stream().filter(filter(prefix, "domain"));
final Cluster core = digraph.addCluster("hexagon");
core.setLabel("Core Domain");
// add all domain model elements first
domain.forEach(new Consumer<ClassInfo>() {
public void accept(ClassInfo ci) {
final Class clazz = ci.load();
core.addNode(clazz.getName()).setLabel(clazz.getSimpleName()).setComment(clazz.getSimpleName());
}
});
Stream<ClassInfo> infra = allClasses.stream().filter(filter(prefix, "infra"));
infra.forEach(new Consumer<ClassInfo>() {
public void accept(ClassInfo ci) {
final Class clazz = ci.load();
digraph.addNode(clazz.getName()).setLabel(clazz.getSimpleName()).setComment(clazz.getSimpleName());
}
});
infra = allClasses.stream().filter(filter(prefix, "infra"));
infra.forEach(new Consumer<ClassInfo>() {
public void accept(ClassInfo ci) {
final Class clazz = ci.load();
// API
for (Field field : clazz.getDeclaredFields()) {
final Class<?> type = field.getType();
if (!type.isPrimitive()) {
digraph.addExistingAssociation(clazz.getName(), type.getName(), null, null,
ASSOCIATION_EDGE_STYLE);
}
}
// SPI
for (Class intf : clazz.getInterfaces()) {
digraph.addExistingAssociation(intf.getName(), clazz.getName(), null, null, IMPLEMENTS_EDGE_STYLE);
}
}
});
// then wire them together
domain = allClasses.stream().filter(filter(prefix, "domain"));
domain.forEach(new Consumer<ClassInfo>() {
public void accept(ClassInfo ci) {
final Class clazz = ci.load();
for (Field field : clazz.getDeclaredFields()) {
final Class<?> type = field.getType();
if (!type.isPrimitive()) {
digraph.addExistingAssociation(clazz.getName(), type.getName(), null, null,
ASSOCIATION_EDGE_STYLE);
}
}
for (Class intf : clazz.getInterfaces()) {
digraph.addExistingAssociation(intf.getName(), clazz.getName(), null, null, IMPLEMENTS_EDGE_STYLE);
}
}
});
// render into image
final Properties p = new Properties();
final String fileName = "graphviz-dot.properties";
final InputStream is = this.getClass().getResourceAsStream(fileName);
if (is == null) {
throw new RuntimeException("Could not load property file '" + fileName);
}
p.load(is);
final DotWriter dotWriter = new DotWriter(p);
final String content = graph.render().trim();
final String imageName = dotWriter.toImage("hexagonal-architecture", content);
assertThat(imageName).isEqualTo("hexagonal-architecture.png");
}
private Predicate<ClassInfo> filter(final String prefix, final String layer) {
return new Predicate<ClassInfo>() {
public boolean test(ClassInfo ci) {
final boolean nameConvention = ci.getPackageName().startsWith(prefix)
&& !ci.getSimpleName().endsWith("Test") && ci.getPackageName().contains("." + layer);
return nameConvention;
}
};
}
}