Skip to content

Commit

Permalink
Merge pull request #7 from davidburkhart/master
Browse files Browse the repository at this point in the history
Cleanup & add target/test-classes to package cycle check
  • Loading branch information
BenRomberg committed Dec 7, 2013
2 parents 32d5de1 + 696dcaf commit e557b7e
Show file tree
Hide file tree
Showing 1,724 changed files with 22,274 additions and 22,227 deletions.
76 changes: 38 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
[![Build Status](https://buildhive.cloudbees.com/job/andrena/job/no-package-cycles-enforcer-rule/badge/icon)](https://buildhive.cloudbees.com/job/andrena/job/no-package-cycles-enforcer-rule/)

This Maven Enforcer Rule checks your project for package cycles. It fails the build if any package cycle is found, showing you the packages and classes involved in the cycle.

Usage: Add the following plugin to your POM:

```
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.2</version>
<dependencies>
<dependency>
<groupId>de.andrena.tools.nopackagecycles</groupId>
<artifactId>no-package-cycles-enforcer-rule</artifactId>
<version>1.0.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>enforce-no-package-cycles</id>
<goals>
<goal>enforce</goal>
</goals>
<phase>compile</phase>
<configuration>
<rules>
<NoPackageCyclesRule implementation="de.andrena.tools.nopackagecycles.NoPackageCyclesRule" />
</rules>
</configuration>
</execution>
</executions>
</plugin>
```

See also:
* The original version by Daniel Seidewitz on [Stackoverflow](http://stackoverflow.com/questions/3416547/maven-jdepend-fail-build-with-cycles). Improved by showing all packages afflicted with cycles and the corresponding classes importing the conflicting packages.
* [JDepend](https://github.com/clarkware/jdepend), the library being used to detect package cycles.
* For more information about package cycles, see ["The Acyclic Dependencies Principle" by Robert C. Martin (Page 6)](http://www.objectmentor.com/resources/articles/granularity.pdf).
[![Build Status](https://buildhive.cloudbees.com/job/andrena/job/no-package-cycles-enforcer-rule/badge/icon)](https://buildhive.cloudbees.com/job/andrena/job/no-package-cycles-enforcer-rule/)

This Maven Enforcer Rule checks your project for package cycles. It fails the build if any package cycle is found, showing you the packages and classes involved in the cycle.

Usage: Add the following plugin to your POM:

```
<plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.2</version>
<dependencies>
<dependency>
<groupId>de.andrena.tools.nopackagecycles</groupId>
<artifactId>no-package-cycles-enforcer-rule</artifactId>
<version>1.0.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>enforce-no-package-cycles</id>
<goals>
<goal>enforce</goal>
</goals>
<phase>test-compile</phase>
<configuration>
<rules>
<NoPackageCyclesRule implementation="de.andrena.tools.nopackagecycles.NoPackageCyclesRule" />
</rules>
</configuration>
</execution>
</executions>
</plugin>
```

See also:
* The original version by Daniel Seidewitz on [Stackoverflow](http://stackoverflow.com/questions/3416547/maven-jdepend-fail-build-with-cycles). Improved by showing all packages afflicted with cycles and the corresponding classes importing the conflicting packages.
* [JDepend](https://github.com/clarkware/jdepend), the library being used to detect package cycles.
* For more information about package cycles, see ["The Acyclic Dependencies Principle" by Robert C. Martin (Page 6)](http://www.objectmentor.com/resources/articles/granularity.pdf).
5 changes: 3 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
<includes>
<include>**/*IntegrationTest.java</include>
</includes>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
Expand All @@ -118,7 +119,7 @@
<dependency>
<groupId>de.andrena.tools.nopackagecycles</groupId>
<artifactId>no-package-cycles-enforcer-rule</artifactId>
<version>1.0.3</version>
<version>1.0.4</version>
</dependency>
</dependencies>
<executions>
Expand Down Expand Up @@ -194,7 +195,7 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
<scope>test</scope>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package de.andrena.tools.nopackagecycles;

import static java.util.Collections.unmodifiableList;

import java.io.File;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;

public class DirectoriesWithClasses implements Iterable<File>{

public static final String MAVEN_PROJECT_BUILD_OUTPUT_DIRECTORY_VAR = "${project.build.outputDirectory}";
public static final String MAVEN_PROJECT_BUILD_TEST_OUTPUT_DIRECTORY_VAR = "${project.build.testOutputDirectory}";

private final List<File> directories = new LinkedList<File>();

public DirectoriesWithClasses(EnforcerRuleHelper helper) throws ExpressionEvaluationException {
addDirectoryIfExists(helper, MAVEN_PROJECT_BUILD_OUTPUT_DIRECTORY_VAR);
addDirectoryIfExists(helper, MAVEN_PROJECT_BUILD_TEST_OUTPUT_DIRECTORY_VAR);
}

private void addDirectoryIfExists(EnforcerRuleHelper helper, String variable)
throws ExpressionEvaluationException {
File directory = new File((String) helper.evaluate(variable));
if(directory.exists()) {
helper.getLog().info("Adding directory " + directory.getAbsolutePath() + " for package cycles search.");
directories.add(directory);
} else {
helper.getLog().info("Directory " + directory.getAbsolutePath() + " could not be found.");
}
}

public boolean directoriesWithClassesFound() {
return !directories.isEmpty();
}

public Iterator<File> iterator() {
return unmodifiableList(directories).iterator();
}
}
Original file line number Diff line number Diff line change
@@ -1,77 +1,70 @@
package de.andrena.tools.nopackagecycles;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

import jdepend.framework.JDepend;
import jdepend.framework.JavaPackage;

import org.apache.maven.enforcer.rule.api.EnforcerRule;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;

public class NoPackageCyclesRule implements EnforcerRule {

public static final String MAVEN_CLASSES_DIR = "classes";
public static final String MAVEN_PROJECT_BUILD_DIRECTORY_VAR = "${project.build.directory}";

public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {
try {
executePackageCycleCheckIfNecessary(helper);
} catch (ExpressionEvaluationException e) {
throw new EnforcerRuleException("Unable to lookup an expression " + e.getLocalizedMessage(), e);
} catch (IOException e) {
throw new EnforcerRuleException("Unable to access target directory " + e.getLocalizedMessage(), e);
}
}

private void executePackageCycleCheckIfNecessary(EnforcerRuleHelper helper) throws ExpressionEvaluationException,
IOException, EnforcerRuleException {
File targetDir = new File((String) helper.evaluate(MAVEN_PROJECT_BUILD_DIRECTORY_VAR));
File classesDir = new File(targetDir, MAVEN_CLASSES_DIR);
helper.getLog().info("Searching directory " + classesDir.getAbsolutePath() + " for package cycles.");
if (checkIsNecessary(classesDir)) {
executePackageCycleCheck(classesDir);
} else {
helper.getLog().info("Directory " + classesDir.getAbsolutePath() + " could not be found.");
}
}

private void executePackageCycleCheck(File classesDir) throws IOException, EnforcerRuleException {
JDepend jdepend = createJDepend();
jdepend.addDirectory(classesDir.getAbsolutePath());
jdepend.analyze();
if (jdepend.containsCycles()) {
throw new EnforcerRuleException("There are package cycles:" + getPackageCycles(jdepend));
}
}

protected JDepend createJDepend() {
return new JDepend();
}

private String getPackageCycles(JDepend jdepend) {
@SuppressWarnings("unchecked")
Collection<JavaPackage> packages = jdepend.getPackages();
return new PackageCycleOutput(new ArrayList<JavaPackage>(packages)).getOutput();
}

private boolean checkIsNecessary(File classesDir) {
return classesDir.exists();
}

public String getCacheId() {
return "";
}

public boolean isCacheable() {
return false;
}

public boolean isResultValid(EnforcerRule arg0) {
return false;
}
package de.andrena.tools.nopackagecycles;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

import jdepend.framework.JDepend;
import jdepend.framework.JavaPackage;

import org.apache.maven.enforcer.rule.api.EnforcerRule;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;

public class NoPackageCyclesRule implements EnforcerRule {

public void execute(EnforcerRuleHelper helper) throws EnforcerRuleException {
try {
executePackageCycleCheckIfNecessary(helper);
} catch (ExpressionEvaluationException e) {
throw new EnforcerRuleException("Unable to lookup an expression " + e.getLocalizedMessage(), e);
} catch (IOException e) {
throw new EnforcerRuleException("Unable to access target directory " + e.getLocalizedMessage(), e);
}
}

private void executePackageCycleCheckIfNecessary(EnforcerRuleHelper helper) throws ExpressionEvaluationException,
IOException, EnforcerRuleException {
DirectoriesWithClasses directories = new DirectoriesWithClasses(helper);
if (directories.directoriesWithClassesFound()) {
executePackageCycleCheck(directories);
} else {
helper.getLog().info("No directories with classes to check for cycles found.");
}
}

private void executePackageCycleCheck(Iterable<File> directories) throws IOException, EnforcerRuleException {
JDepend jdepend = createJDepend();
for (File directory : directories) {
jdepend.addDirectory(directory.getAbsolutePath());
}
jdepend.analyze();
if (jdepend.containsCycles()) {
throw new EnforcerRuleException("There are package cycles:" + getPackageCycles(jdepend));
}
}

protected JDepend createJDepend() {
return new JDepend();
}

private String getPackageCycles(JDepend jdepend) {
@SuppressWarnings("unchecked")
Collection<JavaPackage> packages = jdepend.getPackages();
return new PackageCycleOutput(new ArrayList<JavaPackage>(packages)).getOutput();
}

public String getCacheId() {
return "";
}

public boolean isCacheable() {
return false;
}

public boolean isResultValid(EnforcerRule arg0) {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@
import org.apache.commons.io.IOUtils;
import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import de.andrena.tools.nopackagecycles.mock.EnforcerRuleHelperMock;

Expand All @@ -26,9 +24,6 @@ public class NoPackageCyclesRuleIntegrationTest {
private NoPackageCyclesRule rule;
private EnforcerRuleHelperMock helper;

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Before
public void setUp() throws Exception {
rule = new NoPackageCyclesRule();
Expand All @@ -46,7 +41,8 @@ public void junitIntegrationTest() throws Exception {
}

private void assertPackageCycles(URL targetFolder, URL expectedOutput) throws URISyntaxException, IOException {
helper.setTargetDir(new File(targetFolder.toURI()));
helper.setTestClassesDir(new File("non-existent"));
helper.setClassesDir(new File(targetFolder.toURI()));
try {
rule.execute(helper);
fail("expected EnforcerRuleException");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ protected JDepend createJDepend() {
public void setUp() throws Exception {
jdependMock = new JDependMock();
rule = new NoPackageCyclesRuleMock();
temporaryFolder.newFolder(NoPackageCyclesRule.MAVEN_CLASSES_DIR);
helper = new EnforcerRuleHelperMock();
helper.setTargetDir(temporaryFolder.getRoot());
helper.setClassesDir(temporaryFolder.newFolder("classes"));
helper.setTestClassesDir(temporaryFolder.newFolder("test-classes"));
}

@Test
Expand All @@ -65,13 +65,16 @@ public void result_IsNotValid() {

@Test
public void execute_checkNotNecessary_ClassesDirNotFound() throws Exception {
File newFolder = temporaryFolder.newFolder();
helper.setTargetDir(newFolder);
File nonExistentClassesFolder = new File(temporaryFolder.getRoot(), "non-existent-classes-dir");
helper.setClassesDir(nonExistentClassesFolder);
File nonExistentTestClassesFolder = new File(temporaryFolder.getRoot(), "non-existent-test-classes-dir");
helper.setTestClassesDir(nonExistentTestClassesFolder);
rule.execute(helper);
List<String> infoLogs = helper.getLogMock().getInfo();
assertThat(infoLogs, hasSize(2));
assertSearchingInfo(newFolder, infoLogs);
assertThat(infoLogs.get(1), is("Directory " + getTargetDirectory(newFolder) + " could not be found."));
assertThat(infoLogs, hasSize(3));
assertThat(infoLogs.get(0), is("Directory " + nonExistentClassesFolder.getAbsolutePath() + " could not be found."));
assertThat(infoLogs.get(1), is("Directory " + nonExistentTestClassesFolder.getAbsolutePath() + " could not be found."));
assertThat(infoLogs.get(2), is("No directories with classes to check for cycles found."));
}

@Test
Expand All @@ -94,8 +97,20 @@ public void execute_JdependAddDirectoryFailed_ThrowsException() throws Exception
public void execute_ContainsNoCycles() throws Exception {
rule.execute(helper);
List<String> infoLogs = helper.getLogMock().getInfo();
assertThat(infoLogs, hasSize(1));
assertSearchingInfo(temporaryFolder.getRoot(), infoLogs);
assertThat(infoLogs, hasSize(2));
assertThat(infoLogs.get(0), is("Adding directory " + new File(temporaryFolder.getRoot(), "classes").getAbsolutePath() + " for package cycles search."));
assertThat(infoLogs.get(1), is("Adding directory " + new File(temporaryFolder.getRoot(), "test-classes").getAbsolutePath() + " for package cycles search."));
}

@Test
public void execute_ContainsNoCyclesWithoutTestClasses() throws Exception {
File nonExistentTestClassesDir = new File(temporaryFolder.getRoot(), "does not exist");
helper.setTestClassesDir(nonExistentTestClassesDir);
rule.execute(helper);
List<String> infoLogs = helper.getLogMock().getInfo();
assertThat(infoLogs, hasSize(2));
assertThat(infoLogs.get(0), is("Adding directory " + new File(temporaryFolder.getRoot(), "classes").getAbsolutePath() + " for package cycles search."));
assertThat(infoLogs.get(1), is("Directory " + nonExistentTestClassesDir.getAbsolutePath() + " could not be found."));
}

@Test
Expand All @@ -105,13 +120,4 @@ public void execute_ContainsCycles() throws Exception {
expectedException.expectMessage(containsString("There are package cycles"));
rule.execute(helper);
}

private void assertSearchingInfo(File projectDirectory, List<String> infoLogs) {
assertThat(infoLogs.get(0), is("Searching directory " + getTargetDirectory(projectDirectory)
+ " for package cycles."));
}

private String getTargetDirectory(File newFolder) {
return new File(newFolder, NoPackageCyclesRule.MAVEN_CLASSES_DIR).getAbsolutePath();
}
}
Loading

0 comments on commit e557b7e

Please sign in to comment.