diff --git a/javac-plugin/src/main/java/org/moditect/deptective/internal/handler/PackageReferenceValidator.java b/javac-plugin/src/main/java/org/moditect/deptective/internal/handler/PackageReferenceValidator.java index ea6c14c..740d8d3 100644 --- a/javac-plugin/src/main/java/org/moditect/deptective/internal/handler/PackageReferenceValidator.java +++ b/javac-plugin/src/main/java/org/moditect/deptective/internal/handler/PackageReferenceValidator.java @@ -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 reportedUnconfiguredPackages; - private final PackageDependencies.Builder actualPackageDependencies; + private boolean createDotFile; private String currentPackageName; private Component currentComponent; @@ -79,6 +81,16 @@ public boolean configIsValid() { return false; } + List> 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; } diff --git a/javac-plugin/src/main/java/org/moditect/deptective/internal/log/DeptectiveMessages.java b/javac-plugin/src/main/java/org/moditect/deptective/internal/log/DeptectiveMessages.java index f0d0e10..abd7853 100644 --- a/javac-plugin/src/main/java/org/moditect/deptective/internal/log/DeptectiveMessages.java +++ b/javac-plugin/src/main/java/org/moditect/deptective/internal/log/DeptectiveMessages.java @@ -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,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}" }, }; } diff --git a/javac-plugin/src/main/java/org/moditect/deptective/internal/model/Component.java b/javac-plugin/src/main/java/org/moditect/deptective/internal/model/Component.java index 3203ec1..5b3ff72 100644 --- a/javac-plugin/src/main/java/org/moditect/deptective/internal/model/Component.java +++ b/javac-plugin/src/main/java/org/moditect/deptective/internal/model/Component.java @@ -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. *

@@ -30,7 +33,7 @@ * * @author Gunnar Morling */ -public class Component { +public class Component implements Node { public static class Builder { @@ -122,4 +125,51 @@ public Map getReads() { public String toString() { return name + " { contained=" + contained + ", reads=" + reads + "] }"; } + + @Override + public String asShortString() { + return name; + } + + @Override + public Dependency getOutgoingDependencyTo(Component node) { + return reads.entrySet() + .stream() + .filter(e -> e.getKey().equals(node.getName())) + .map(e -> new Dependency(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; + } + } diff --git a/javac-plugin/src/main/java/org/moditect/deptective/internal/model/PackageDependencies.java b/javac-plugin/src/main/java/org/moditect/deptective/internal/model/PackageDependencies.java index a24b5f5..9afa0c7 100644 --- a/javac-plugin/src/main/java/org/moditect/deptective/internal/model/PackageDependencies.java +++ b/javac-plugin/src/main/java/org/moditect/deptective/internal/model/PackageDependencies.java @@ -142,4 +142,8 @@ public boolean isWhitelisted(String packageName) { .findFirst() .isPresent(); } + + public Iterable getComponents() { + return components; + } } diff --git a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/CycleTest.java b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/CycleTest.java new file mode 100644 index 0000000..adc5969 --- /dev/null +++ b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/CycleTest.java @@ -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"); + } +} diff --git a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/bar/Bar.java b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/bar/Bar.java new file mode 100644 index 0000000..12e5207 --- /dev/null +++ b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/bar/Bar.java @@ -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; +} diff --git a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/baz/Baz.java b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/baz/Baz.java new file mode 100644 index 0000000..bae3b15 --- /dev/null +++ b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/baz/Baz.java @@ -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; +} diff --git a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/foo/Foo.java b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/foo/Foo.java new file mode 100644 index 0000000..19dbb71 --- /dev/null +++ b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/foo/Foo.java @@ -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(); +} diff --git a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/qux/Qux.java b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/qux/Qux.java new file mode 100644 index 0000000..abb85ae --- /dev/null +++ b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/cycle/qux/Qux.java @@ -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; +} diff --git a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/whitelist/bar/Bar.java b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/whitelist/bar/Bar.java index c0a5289..f742590 100644 --- a/javac-plugin/src/test/java/org/moditect/deptective/plugintest/whitelist/bar/Bar.java +++ b/javac-plugin/src/test/java/org/moditect/deptective/plugintest/whitelist/bar/Bar.java @@ -15,6 +15,9 @@ */ package org.moditect.deptective.plugintest.whitelist.bar; +import org.moditect.deptective.plugintest.whitelist.foo.Foo; + public class Bar { + Foo f; } diff --git a/javac-plugin/src/test/resources/org/moditect/deptective/plugintest/cycle/deptective.json b/javac-plugin/src/test/resources/org/moditect/deptective/plugintest/cycle/deptective.json new file mode 100644 index 0000000..58bce94 --- /dev/null +++ b/javac-plugin/src/test/resources/org/moditect/deptective/plugintest/cycle/deptective.json @@ -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" ] + } + ] +}