Skip to content

Commit

Permalink
moditect#27 WIP Adding cycle detection to architecture validation
Browse files Browse the repository at this point in the history
  • Loading branch information
gunnarmorling committed Jan 27, 2019
1 parent 2e21d50 commit 396ba1f
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

Expand All @@ -26,6 +27,8 @@
import javax.tools.StandardLocation;

import org.moditect.deptective.internal.export.DotSerializer;
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 All @@ -50,12 +53,11 @@ public class PackageReferenceValidator implements PackageReferenceHandler {
private final PackageDependencies allowedPackageDependencies;
private final JavaFileManager jfm;
private final ReportingPolicy reportingPolicy;
private boolean createDotFile;
private final ReportingPolicy unconfiguredPackageReportingPolicy;
private final Map<String, Boolean> reportedUnconfiguredPackages;

private final PackageDependencies.Builder actualPackageDependencies;

private boolean createDotFile;
private String currentPackageName;
private Component currentComponent;

Expand All @@ -79,6 +81,16 @@ public boolean configIsValid() {
return false;
}

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

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

log.report(ReportingPolicy.ERROR, DeptectiveMessages.CYCLE_IN_ARCHITECTURE, cyclesAsString);
}

return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public class DeptectiveMessages extends ListResourceBundle {
public static final String GENERATED_CONFIG = "deptective.generatedconfig";
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";

@Override
protected final Object[][] getContents() {
Expand All @@ -43,6 +44,9 @@ protected final Object[][] getContents() {
"Created DOT file representing the Deptective configuration at {0}" },
{ ERROR_PREFIX + PACKAGE_CONTAINED_IN_MULTIPLE_COMPONENTS,
"Multiple components match package {1}: {0}" },
{ ERROR_PREFIX + CYCLE_IN_ARCHITECTURE,
"Architecture model contains cycle(s) between these components: " + System.lineSeparator()
+ "{0}" },
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
import java.util.Map;
import java.util.Set;

import org.moditect.deptective.internal.graph.Dependency;
import org.moditect.deptective.internal.graph.Node;

/**
* Describes a component, a set of packages identified by one more naming patterns.
* <p>
Expand All @@ -30,7 +33,7 @@
*
* @author Gunnar Morling
*/
public class Component {
public class Component implements Node<Component> {

public static class Builder {

Expand Down Expand Up @@ -122,4 +125,51 @@ public Map<String, ReadKind> getReads() {
public String toString() {
return name + " { contained=" + contained + ", reads=" + reads + "] }";
}

@Override
public String asShortString() {
return name;
}

@Override
public Dependency<Component> getOutgoingDependencyTo(Component node) {
return reads.entrySet()
.stream()
.filter(e -> e.getKey().equals(node.getName()))
.map(e -> new Dependency<Component>(Component.builder(e.getKey()).build(), 1))
.findFirst()
.orElse(null);
}

@Override
public boolean hasOutgoingDependencies() {
return !reads.isEmpty();
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Component other = (Component) obj;
if (name == null) {
if (other.name != null)
return false;
}
else if (!name.equals(other.name))
return false;
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,8 @@ public boolean isWhitelisted(String packageName) {
.findFirst()
.isPresent();
}

public Iterable<Component> getComponents() {
return components;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2019 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.deptective.plugintest.cycle;

import static com.google.testing.compile.CompilationSubject.assertThat;

import org.junit.Test;
import org.moditect.deptective.plugintest.PluginTestBase;
import org.moditect.deptective.plugintest.cycle.bar.Bar;
import org.moditect.deptective.plugintest.cycle.baz.Baz;
import org.moditect.deptective.plugintest.cycle.foo.Foo;
import org.moditect.deptective.plugintest.cycle.qux.Qux;

import com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;

public class CycleTest extends PluginTestBase {

@Test
public void shouldDetectCyclesInArchitectureModel() {
Compilation compilation = Compiler.javac()
.withOptions(
"-Xplugin:Deptective",
getConfigFileOption()
)
.compile(
forTestClass(Foo.class),
forTestClass(Bar.class),
forTestClass(Baz.class),
forTestClass(Qux.class)
);

assertThat(compilation).failed();
assertThat(compilation).hadErrorContaining("Architecture model contains cycle(s) between these components:");
assertThat(compilation).hadErrorContaining(" - bar, baz, foo, qux");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Copyright 2019 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.deptective.plugintest.cycle.bar;

import org.moditect.deptective.plugintest.cycle.baz.Baz;
import org.moditect.deptective.plugintest.cycle.qux.Qux;

public class Bar {

Baz baz;
Qux qux;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright 2019 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.deptective.plugintest.cycle.baz;

import org.moditect.deptective.plugintest.cycle.foo.Foo;

public class Baz {

private Foo foo;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright 2019 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.deptective.plugintest.cycle.foo;

import org.moditect.deptective.plugintest.cycle.bar.Bar;

public class Foo {

private final Bar bar = new Bar();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Copyright 2019 The ModiTect authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.moditect.deptective.plugintest.cycle.qux;

import org.moditect.deptective.plugintest.cycle.bar.Bar;

public class Qux {

Bar bar;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package org.moditect.deptective.plugintest.whitelist.bar;

import org.moditect.deptective.plugintest.whitelist.foo.Foo;

public class Bar {

Foo f;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"components" : [
{
"name" : "foo",
"contains" : [ "org.moditect.deptective.plugintest.cycle.foo" ],
"reads" : [ "bar" ]
},
{
"name" : "bar",
"contains" : [ "org.moditect.deptective.plugintest.cycle.bar" ],
"reads" : [ "baz", "qux" ]
},
{
"name" : "baz",
"contains" : [ "org.moditect.deptective.plugintest.cycle.baz" ],
"reads" : [ "foo" ]
},
{
"name" : "qux",
"contains" : [ "org.moditect.deptective.plugintest.cycle.qux" ],
"reads" : [ "bar" ]
}
]
}

0 comments on commit 396ba1f

Please sign in to comment.