Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
moditect#27 WIP Adding cycle detection to architecture validation
Browse files Browse the repository at this point in the history
gunnarmorling committed Jan 27, 2019

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 5b13205 commit 7ca857b
Showing 10 changed files with 216 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -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;

@@ -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;
@@ -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;

@@ -79,6 +81,18 @@ 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 false;
}

return true;
}

Original file line number Diff line number Diff line change
@@ -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() {
@@ -43,6 +44,8 @@ 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 component cycle(s): " + System.lineSeparator() + "{0}" },
};
}

Original file line number Diff line number Diff line change
@@ -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>
@@ -30,7 +33,7 @@
*
* @author Gunnar Morling
*/
public class Component {
public class Component implements Node<Component> {

public static class Builder {

@@ -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
@@ -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,51 @@
/**
* 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.Ignore;
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 com.google.testing.compile.Compilation;
import com.google.testing.compile.Compiler;

public class CycleTest extends PluginTestBase {

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

assertThat(compilation).failed();
assertThat(compilation).hadErrorContaining(
"Architecture model contains component cycle(s): foo -> bar -> baz -> 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.bar;

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

public class Bar {

Baz baz;
}
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
@@ -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,19 @@
{
"components" : [
{
"name" : "foo",
"contains" : [ "org.moditect.deptective.plugintest.cycle.foo" ],
"reads" : [ "bar" ]
},
{
"name" : "bar",
"contains" : [ "org.moditect.deptective.plugintest.cycle.bar" ],
"reads" : [ "baz" ]
},
{
"name" : "baz",
"contains" : [ "org.moditect.deptective.plugintest.cycle.baz" ],
"reads" : [ "foo" ]
}
]
}

0 comments on commit 7ca857b

Please sign in to comment.