diff --git a/README.md b/README.md
index 653b0c8..1ca4564 100644
--- a/README.md
+++ b/README.md
@@ -210,5 +210,8 @@ Unresolvable methods (no coverage information available):
1. exception: completely stateless `*Utils` classes with static methods only
1. utility classes must be `abstract` and have a private default constructor
1. exception: real constants with names in upper case delimited by underscores
-1. `logger` has to be protected final but not static: `protected final Logger logger = LoggerFactory.getLogger(getClass());`
+1. `logger` has to be protected final but not static: `protected final Logger logger = LoggerFactory.getLogger(getClass());` (see https://www.slf4j.org/faq.html#declared_static)
1. restrict file, method and lambda lengths to reasonable values
+1. code dependency
+ 1. no code cycles on package level
+ 1. no dependencies between a package and any of its (sub-)sub-packages (only the other way around)
diff --git a/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/DebugCoverageResolutionMojo.java b/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/DebugCoverageResolutionMojo.java
index bba66d3..990c909 100644
--- a/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/DebugCoverageResolutionMojo.java
+++ b/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/DebugCoverageResolutionMojo.java
@@ -1,7 +1,7 @@
package com.scheible.testgapanalysis.maven;
-import com.scheible.testgapanalysis.DebugCoverageResolution;
-import com.scheible.testgapanalysis.DebugCoverageResolutionReport;
+import com.scheible.testgapanalysis.debug.DebugCoverageResolution;
+import com.scheible.testgapanalysis.debug.DebugCoverageResolutionReport;
import com.scheible.testgapanalysis.jacoco.JaCoCoReportParser;
import com.scheible.testgapanalysis.parser.JavaParser;
diff --git a/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/TestGapAnalysisMojo.java b/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/TestGapAnalysisMojo.java
index 549065c..60eecf7 100644
--- a/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/TestGapAnalysisMojo.java
+++ b/test-gap-analysis-maven-plugin/src/main/java/com/scheible/testgapanalysis/maven/TestGapAnalysisMojo.java
@@ -2,8 +2,8 @@
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
-import com.scheible.testgapanalysis.TestGapAnalysis;
-import com.scheible.testgapanalysis.TestGapReport;
+import com.scheible.testgapanalysis.analysis.testgap.TestGapAnalysis;
+import com.scheible.testgapanalysis.analysis.testgap.TestGapReport;
import com.scheible.testgapanalysis.analysis.Analysis;
import com.scheible.testgapanalysis.git.GitDiffer;
import com.scheible.testgapanalysis.jacoco.JaCoCoReportParser;
diff --git a/test-gap-analysis/pom.xml b/test-gap-analysis/pom.xml
index 5c4c6ac..ec579a0 100644
--- a/test-gap-analysis/pom.xml
+++ b/test-gap-analysis/pom.xml
@@ -104,6 +104,11 @@
archunit
${archunit.version}
+
+ com.tngtech.archunit
+ archunit-junit4
+ ${archunit.version}
+
org.eclipse.jgit
@@ -163,6 +168,11 @@
archunit
test
+
+ com.tngtech.archunit
+ archunit-junit4
+ test
+
com.scheible.pocketsaw.impl
diff --git a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/TestGapAnalysis.java b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/analysis/testgap/TestGapAnalysis.java
similarity index 94%
rename from test-gap-analysis/src/main/java/com/scheible/testgapanalysis/TestGapAnalysis.java
rename to test-gap-analysis/src/main/java/com/scheible/testgapanalysis/analysis/testgap/TestGapAnalysis.java
index 0e8b250..de22a5e 100644
--- a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/TestGapAnalysis.java
+++ b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/analysis/testgap/TestGapAnalysis.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.analysis.testgap;
import static java.util.Collections.emptySet;
@@ -12,12 +12,12 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import com.scheible.testgapanalysis.TestGapReport.CoverageReportMethod;
-import com.scheible.testgapanalysis.TestGapReport.NewOrChangedFile;
-import com.scheible.testgapanalysis.TestGapReport.NewOrChangedFile.State;
-import com.scheible.testgapanalysis.TestGapReport.TestGapMethod;
import com.scheible.testgapanalysis.analysis.Analysis;
import com.scheible.testgapanalysis.analysis.AnalysisResult;
+import com.scheible.testgapanalysis.analysis.testgap.TestGapReport.CoverageReportMethod;
+import com.scheible.testgapanalysis.analysis.testgap.TestGapReport.NewOrChangedFile;
+import com.scheible.testgapanalysis.analysis.testgap.TestGapReport.NewOrChangedFile.State;
+import com.scheible.testgapanalysis.analysis.testgap.TestGapReport.TestGapMethod;
import com.scheible.testgapanalysis.common.FilesUtils;
import com.scheible.testgapanalysis.git.GitDiffer;
import com.scheible.testgapanalysis.git.RepositoryStatus;
diff --git a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/TestGapReport.java b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/analysis/testgap/TestGapReport.java
similarity index 99%
rename from test-gap-analysis/src/main/java/com/scheible/testgapanalysis/TestGapReport.java
rename to test-gap-analysis/src/main/java/com/scheible/testgapanalysis/analysis/testgap/TestGapReport.java
index 54d71bd..c119560 100644
--- a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/TestGapReport.java
+++ b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/analysis/testgap/TestGapReport.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.analysis.testgap;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
diff --git a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/DebugCoverageResolution.java b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/debug/DebugCoverageResolution.java
similarity index 98%
rename from test-gap-analysis/src/main/java/com/scheible/testgapanalysis/DebugCoverageResolution.java
rename to test-gap-analysis/src/main/java/com/scheible/testgapanalysis/debug/DebugCoverageResolution.java
index 5b6f4b8..285a1b9 100644
--- a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/DebugCoverageResolution.java
+++ b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/debug/DebugCoverageResolution.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.debug;
import java.io.File;
import java.io.IOException;
diff --git a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/DebugCoverageResolutionReport.java b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/debug/DebugCoverageResolutionReport.java
similarity index 97%
rename from test-gap-analysis/src/main/java/com/scheible/testgapanalysis/DebugCoverageResolutionReport.java
rename to test-gap-analysis/src/main/java/com/scheible/testgapanalysis/debug/DebugCoverageResolutionReport.java
index ecad37f..00ef482 100644
--- a/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/DebugCoverageResolutionReport.java
+++ b/test-gap-analysis/src/main/java/com/scheible/testgapanalysis/debug/DebugCoverageResolutionReport.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.debug;
import static java.util.Collections.unmodifiableMap;
import static java.util.Collections.unmodifiableSet;
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/CodeDependenciesTest.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/CodeDependenciesTest.java
new file mode 100644
index 0000000..55831ed
--- /dev/null
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/CodeDependenciesTest.java
@@ -0,0 +1,71 @@
+package com.scheible.testgapanalysis;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
+
+import org.junit.runner.RunWith;
+
+import com.tngtech.archunit.core.domain.Dependency;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.junit.ArchUnitRunner;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import com.tngtech.archunit.library.dependencies.SliceAssignment;
+import com.tngtech.archunit.library.dependencies.SliceIdentifier;
+
+/**
+ *
+ * @author sj
+ */
+@RunWith(ArchUnitRunner.class)
+@AnalyzeClasses(packagesOf = CodeDependenciesTest.class, importOptions = DoNotIncludeTests.class)
+public class CodeDependenciesTest {
+
+ private static class SlicePerPackage implements SliceAssignment {
+
+ @Override
+ public SliceIdentifier getIdentifierOf(final JavaClass javaClass) {
+ return SliceIdentifier.of(javaClass.getPackageName());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Every package is treated as a slice.";
+ }
+ }
+
+ @ArchTest
+ static final ArchRule noPackageCyclesRule = slices().assignedFrom(new SlicePerPackage()).should().beFreeOfCycles();
+
+ private static class DependOnDescendantPackagesCondition extends ArchCondition {
+
+ DependOnDescendantPackagesCondition() {
+ super("depend on descendant packages");
+ }
+
+ @Override
+ public void check(JavaClass clazz, ConditionEvents events) {
+ for (Dependency dependency : clazz.getDirectDependenciesFromSelf()) {
+ boolean dependencyOnDescendantPackage = isDependencyOnDescendantPackage(dependency.getOriginClass(),
+ dependency.getTargetClass());
+ events.add(new SimpleConditionEvent(dependency, dependencyOnDescendantPackage,
+ dependency.getDescription()));
+ }
+ }
+
+ private boolean isDependencyOnDescendantPackage(JavaClass origin, JavaClass target) {
+ String originPackageName = origin.getPackageName();
+ String targetSubPackagePrefix = target.getPackageName();
+ return targetSubPackagePrefix.contains(originPackageName + ".");
+ }
+ }
+
+ @ArchTest
+ static final ArchRule packageLayeringRule = noClasses().should(new DependOnDescendantPackagesCondition())
+ .because("lower packages shouldn't build on higher packages");
+}
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisSubModule.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisSubModule.java
index 84b63d9..e08a43e 100644
--- a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisSubModule.java
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisSubModule.java
@@ -1,20 +1,12 @@
package com.scheible.testgapanalysis;
import com.scheible.pocketsaw.api.SubModule;
-import com.scheible.testgapanalysis.ExternalFunctionalities.Slf4j;
-import com.scheible.testgapanalysis.analysis.AnalysisSubModule;
-import com.scheible.testgapanalysis.common.CommonSubModule;
-import com.scheible.testgapanalysis.git.GitSubModule;
-import com.scheible.testgapanalysis.jacoco.JaCoCoSubModule;
-import com.scheible.testgapanalysis.jacoco.resolver.JaCoCoResolverSubModule;
-import com.scheible.testgapanalysis.parser.ParserSubModule;
/**
*
* @author sj
*/
-@SubModule(includeSubPackages = false, uses = {AnalysisSubModule.class, JaCoCoSubModule.class, ParserSubModule.class,
- JaCoCoResolverSubModule.class, Slf4j.class, GitSubModule.class, CommonSubModule.class})
+@SubModule(includeSubPackages = false)
public class TestGapAnalysisSubModule {
}
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/AnalysisSubModule.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/AnalysisSubModule.java
index 5f0e0bc..95ec973 100644
--- a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/AnalysisSubModule.java
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/AnalysisSubModule.java
@@ -2,6 +2,7 @@
import com.scheible.pocketsaw.api.SubModule;
import com.scheible.testgapanalysis.ExternalFunctionalities.Slf4j;
+import com.scheible.testgapanalysis.common.CommonSubModule;
import com.scheible.testgapanalysis.git.GitSubModule;
import com.scheible.testgapanalysis.jacoco.JaCoCoSubModule;
import com.scheible.testgapanalysis.jacoco.resolver.JaCoCoResolverSubModule;
@@ -12,7 +13,7 @@
* @author sj
*/
@SubModule(uses = {JaCoCoSubModule.class, JaCoCoResolverSubModule.class, GitSubModule.class, ParserSubModule.class,
- Slf4j.class})
+ Slf4j.class, CommonSubModule.class})
public class AnalysisSubModule {
}
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/AnalysisTestGapSubModule.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/AnalysisTestGapSubModule.java
new file mode 100644
index 0000000..c56d1cd
--- /dev/null
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/AnalysisTestGapSubModule.java
@@ -0,0 +1,20 @@
+package com.scheible.testgapanalysis.analysis.testgap;
+
+import com.scheible.pocketsaw.api.SubModule;
+import com.scheible.testgapanalysis.ExternalFunctionalities.Slf4j;
+import com.scheible.testgapanalysis.analysis.*;
+import com.scheible.testgapanalysis.common.CommonSubModule;
+import com.scheible.testgapanalysis.git.GitSubModule;
+import com.scheible.testgapanalysis.jacoco.JaCoCoSubModule;
+import com.scheible.testgapanalysis.jacoco.resolver.JaCoCoResolverSubModule;
+import com.scheible.testgapanalysis.parser.ParserSubModule;
+
+/**
+ *
+ * @author sj
+ */
+@SubModule(uses = {JaCoCoSubModule.class, JaCoCoResolverSubModule.class, GitSubModule.class, ParserSubModule.class,
+ Slf4j.class, CommonSubModule.class})
+public class AnalysisTestGapSubModule {
+
+}
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisTest.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/TestGapAnalysisTest.java
similarity index 96%
rename from test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisTest.java
rename to test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/TestGapAnalysisTest.java
index d2fc06f..a2ad649 100644
--- a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapAnalysisTest.java
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/TestGapAnalysisTest.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.analysis.testgap;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapReportTest.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/TestGapReportTest.java
similarity index 91%
rename from test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapReportTest.java
rename to test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/TestGapReportTest.java
index 3c016e2..ff56d65 100644
--- a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/TestGapReportTest.java
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/analysis/testgap/TestGapReportTest.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.analysis.testgap;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/DebugCoverageResolutionTest.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/debug/DebugCoverageResolutionTest.java
similarity index 94%
rename from test-gap-analysis/src/test/java/com/scheible/testgapanalysis/DebugCoverageResolutionTest.java
rename to test-gap-analysis/src/test/java/com/scheible/testgapanalysis/debug/DebugCoverageResolutionTest.java
index 2e948be..509bda0 100644
--- a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/DebugCoverageResolutionTest.java
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/debug/DebugCoverageResolutionTest.java
@@ -1,4 +1,4 @@
-package com.scheible.testgapanalysis;
+package com.scheible.testgapanalysis.debug;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/debug/DebugSubModule.java b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/debug/DebugSubModule.java
new file mode 100644
index 0000000..34a9119
--- /dev/null
+++ b/test-gap-analysis/src/test/java/com/scheible/testgapanalysis/debug/DebugSubModule.java
@@ -0,0 +1,16 @@
+package com.scheible.testgapanalysis.debug;
+
+import com.scheible.pocketsaw.api.SubModule;
+import com.scheible.testgapanalysis.common.CommonSubModule;
+import com.scheible.testgapanalysis.jacoco.JaCoCoSubModule;
+import com.scheible.testgapanalysis.jacoco.resolver.JaCoCoResolverSubModule;
+import com.scheible.testgapanalysis.parser.ParserSubModule;
+
+/**
+ *
+ * @author sj
+ */
+@SubModule(uses = {JaCoCoSubModule.class, ParserSubModule.class, JaCoCoResolverSubModule.class, CommonSubModule.class})
+public class DebugSubModule {
+
+}