Skip to content

Commit

Permalink
#27 Adding cycle detection to architecture analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
gunnarmorling committed Jan 27, 2019
1 parent 1669f09 commit f139948
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.moditect.deptective.internal.model.Components;
import org.moditect.deptective.internal.model.PackageDependencies;
import org.moditect.deptective.internal.options.DeptectiveOptions;
import org.moditect.deptective.internal.options.ReportingPolicy;

/**
* Describes the {@link PackageReferenceHandler} to be invoked when traversing the ASTs of the project under
Expand All @@ -48,7 +49,7 @@ public PackageReferenceHandler getPackageReferenceHandler(JavaFileManager jfm, D
configSupplier.get(),
options.getReportingPolicy(),
options.getUnconfiguredPackageReportingPolicy(),
options.getCycleReportingPolicy(),
options.getCycleReportingPolicy(ReportingPolicy.ERROR),
options.createDotFile(),
log
);
Expand All @@ -73,6 +74,7 @@ public PackageReferenceHandler getPackageReferenceHandler(JavaFileManager jfm, D
jfm,
log,
options.getWhitelistedPackagePatterns(),
options.getCycleReportingPolicy(ReportingPolicy.WARN),
new Components(components),
options.createDotFile()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import org.moditect.deptective.internal.export.DotSerializer;
import org.moditect.deptective.internal.export.JsonSerializer;
import org.moditect.deptective.internal.export.ModelSerializer;
import org.moditect.deptective.internal.graph.Cycle;
import org.moditect.deptective.internal.graph.GraphUtils;
import org.moditect.deptective.internal.log.DeptectiveMessages;
import org.moditect.deptective.internal.log.Log;
import org.moditect.deptective.internal.model.Component;
Expand Down Expand Up @@ -58,6 +60,7 @@ public class PackageReferenceCollector implements PackageReferenceHandler {

private final JavaFileManager jfm;
private final List<PackagePattern> whitelistPatterns;
private final ReportingPolicy cycleReportingPolicy;

/**
* Any components that were declared externally.
Expand All @@ -71,10 +74,11 @@ public class PackageReferenceCollector implements PackageReferenceHandler {
private boolean createOutputFile = true;

public PackageReferenceCollector(JavaFileManager jfm, Log log, List<PackagePattern> whitelistPatterns,
Components declaredComponents, boolean createDotFile) {
ReportingPolicy cycleReportingPolicy, Components declaredComponents, boolean createDotFile) {
this.log = log;
this.jfm = jfm;
this.whitelistPatterns = Collections.unmodifiableList(whitelistPatterns);
this.cycleReportingPolicy = cycleReportingPolicy;
this.declaredComponents = declaredComponents;
this.createDotFile = createDotFile;

Expand Down Expand Up @@ -185,7 +189,31 @@ public void onCompletingCompilation() {
throw new RuntimeException("Failed to write deptective.json file", e);
}

List<Cycle<Component>> cycles = GraphUtils.detectCycles(packageDependencies.getComponents());

if (!cycles.isEmpty()) {
String cyclesAsString = "- " + cycles.stream()
.map(Cycle::toString)
.collect(Collectors.joining("," + System.lineSeparator() + "- "));

log.report(cycleReportingPolicy, DeptectiveMessages.CYCLE_IN_CODE_BASE, cyclesAsString);
}

if (createDotFile) {
if (!cycles.isEmpty()) {
for (Component.Builder component : builder.getComponents()) {
for (Cycle<Component> cycle : cycles) {
if (contains(cycle, component.getName())) {
for (Component nodeInCycle : cycle.getNodes()) {
if (component.getReads().containsKey(nodeInCycle.getName())) {
component.addRead(nodeInCycle.getName(), ReadKind.CYCLE);
}
}
}
}
}
}

serializer = new DotSerializer();
packageDependencies.serialize(serializer);

Expand All @@ -202,6 +230,16 @@ public void onCompletingCompilation() {
}
}

private boolean contains(Cycle<Component> cycle, String name) {
for (Component component : cycle.getNodes()) {
if (component.getName().equals(name)) {
return true;
}
}

return false;
}

private boolean isWhitelistAllExternal() {
return whitelistPatterns.contains(PackagePattern.ALL_EXTERNAL);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class DeptectiveMessages extends ListResourceBundle {
public static final String GENERATED_DOT_REPRESENTATION = "deptective.dotrepresentation";
public static final String PACKAGE_CONTAINED_IN_MULTIPLE_COMPONENTS = "deptective.packageinmultiplecomponents";
public static final String CYCLE_IN_ARCHITECTURE = "deptective.cycleinarchitecture";
public static final String CYCLE_IN_CODE_BASE = "deptective.cycleincodebase";

@Override
protected final Object[][] getContents() {
Expand All @@ -50,6 +51,12 @@ protected final Object[][] getContents() {
{ WARNING_PREFIX + CYCLE_IN_ARCHITECTURE,
"Architecture model contains cycle(s) between these components: " + System.lineSeparator()
+ "{0}" },
{ ERROR_PREFIX + CYCLE_IN_CODE_BASE,
"Analysed code base contains cycle(s) between these components: " + System.lineSeparator()
+ "{0}" },
{ WARNING_PREFIX + CYCLE_IN_CODE_BASE,
"Analysed code base contains cycle(s) between these components: " + System.lineSeparator()
+ "{0}" },
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ public ReportingPolicy getUnconfiguredPackageReportingPolicy() {
/**
* Returns the policy for reporting cycles between components.
*/
public ReportingPolicy getCycleReportingPolicy() {
public ReportingPolicy getCycleReportingPolicy(ReportingPolicy defaultPolicy) {
String policy = options.get("deptective.cycle_reporting_policy");

if (policy != null) {
return ReportingPolicy.valueOf(policy.trim().toUpperCase());
}
else {
return ReportingPolicy.ERROR;
return defaultPolicy;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,24 @@ public void shouldGenerateDotFileForAnalyse() throws Exception {
);
assertThat(compilation).hadNoteCount(2);

assertThat(compilation).hadWarningContaining("Analysed code base contains cycle(s) between these components:");
assertThat(compilation).hadWarningContaining(
" - org.moditect.deptective.plugintest.visualize.bar, org.moditect.deptective.plugintest.visualize.qux"
);

String expectedConfig = Strings.lines(
"digraph \"package dependencies\"",
"{",
" \"org.moditect.deptective.plugintest.visualize.bar\";",
" \"org.moditect.deptective.plugintest.visualize.foo\";",
" \"org.moditect.deptective.plugintest.visualize.qux\";",
" subgraph Allowed {",
" \"org.moditect.deptective.plugintest.visualize.bar\" -> \"org.moditect.deptective.plugintest.visualize.qux\";",
" \"org.moditect.deptective.plugintest.visualize.foo\" -> \"org.moditect.deptective.plugintest.visualize.bar\";",
" \"org.moditect.deptective.plugintest.visualize.foo\" -> \"org.moditect.deptective.plugintest.visualize.qux\";",
" }",
" subgraph Cycle {",
" edge [color=purple]",
" \"org.moditect.deptective.plugintest.visualize.bar\" -> \"org.moditect.deptective.plugintest.visualize.qux\";",
" \"org.moditect.deptective.plugintest.visualize.qux\" -> \"org.moditect.deptective.plugintest.visualize.bar\";",
" }",
"}"
Expand Down

0 comments on commit f139948

Please sign in to comment.