├── src ├── test │ ├── resources │ │ └── test-project │ │ │ ├── target │ │ │ └── classes │ │ │ │ └── org │ │ │ │ └── codefx │ │ │ │ └── mvn │ │ │ │ └── jdeps │ │ │ │ └── testproject │ │ │ │ ├── OnActions.class │ │ │ │ ├── OnBASE64.class │ │ │ │ └── OnUnsafe.class │ │ │ ├── src │ │ │ └── main │ │ │ │ └── java │ │ │ │ └── org │ │ │ │ └── codefx │ │ │ │ └── mvn │ │ │ │ └── jdeps │ │ │ │ └── testproject │ │ │ │ ├── OnUnsafe.java │ │ │ │ ├── OnActions.java │ │ │ │ └── OnBASE64.java │ │ │ └── pom.xml │ └── java │ │ └── org │ │ └── codefx │ │ └── mvn │ │ └── jdeps │ │ ├── tool │ │ └── jdeps │ │ │ ├── JavaHomeSystemPropertyJDepsSearchTest.java │ │ │ ├── JavaHomeEnvironmentVariableJDepsSearchTest.java │ │ │ └── JdkInternalsExecutorTest.java │ │ ├── rules │ │ ├── MapDependencyJudgeTest.java │ │ ├── AbstractFlatDependencyJudgeTest.java │ │ ├── AbstractHierarchicalDependencyJudgeTest.java │ │ ├── XmlRuleTest.java │ │ ├── DependenyRuleTest.java │ │ ├── TypeNameHierarchyTest.java │ │ ├── ArrowRuleParserTest.java │ │ └── AbstractDependencyJudgeTest.java │ │ ├── Factory.java │ │ ├── mojo │ │ ├── JdkInternalsExecutionServiceTest.java │ │ └── DependencyRulesConfigurationTest.java │ │ ├── result │ │ ├── ViolationsToRuleTransformerTest.java │ │ └── AnnotatedViolationTest.java │ │ ├── dependency │ │ ├── TypeTest.java │ │ └── ViolationTest.java │ │ └── parse │ │ ├── InternalTypeLineParserTest.java │ │ └── ViolationParserTest.java └── main │ └── java │ └── org │ └── codefx │ └── mvn │ └── jdeps │ ├── result │ ├── ResultOutputStrategy.java │ ├── SystemOutResultOutputStrategy.java │ ├── ViolationsToRuleTransformer.java │ ├── AnnotatedInternalType.java │ ├── FailBuildResultOutputStrategy.java │ ├── Result.java │ ├── ResultBuilder.java │ ├── RuleOutputStrategy.java │ ├── RuleOutputFormat.java │ ├── AnnotatedViolation.java │ └── LogResultOutputStrategy.java │ ├── rules │ ├── PackageInclusion.java │ ├── Arrow.java │ ├── Severity.java │ ├── SimpleDependencyJudge.java │ ├── DependencyJudgeBuilder.java │ ├── XmlRule.java │ ├── TypeNameHierarchy.java │ ├── ArrowRuleParser.java │ ├── MapDependencyJudge.java │ ├── DependencyRule.java │ └── DependencyJudge.java │ ├── tool │ ├── jdeps │ │ ├── JDepsSearch.java │ │ ├── MavenToolchainJDepsSearch.java │ │ ├── ComposedJDepsSearch.java │ │ ├── SearchJDepsInJdk.java │ │ ├── JavaHomeSystemPropertyJDepsSearch.java │ │ ├── JavaHomeEnvironmentVariableJDepsSearch.java │ │ └── JdkInternalsExecutor.java │ ├── PairCollector.java │ └── LineWriter.java │ ├── mojo │ ├── MojoLogging.java │ ├── JdkInternalsExecutionService.java │ ├── OutputConfiguration.java │ ├── JdkInternalsMojo.java │ └── DependencyRulesConfiguration.java │ ├── dependency │ ├── InternalType.java │ ├── Type.java │ └── Violation.java │ └── parse │ ├── InternalTypeLineParser.java │ └── ViolationParser.java ├── .gitignore ├── README.md └── pom.xml /src/test/resources/test-project/target/classes/org/codefx/mvn/jdeps/testproject/OnActions.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipafx/JDeps-Maven-Plugin/HEAD/src/test/resources/test-project/target/classes/org/codefx/mvn/jdeps/testproject/OnActions.class -------------------------------------------------------------------------------- /src/test/resources/test-project/target/classes/org/codefx/mvn/jdeps/testproject/OnBASE64.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipafx/JDeps-Maven-Plugin/HEAD/src/test/resources/test-project/target/classes/org/codefx/mvn/jdeps/testproject/OnBASE64.class -------------------------------------------------------------------------------- /src/test/resources/test-project/target/classes/org/codefx/mvn/jdeps/testproject/OnUnsafe.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nipafx/JDeps-Maven-Plugin/HEAD/src/test/resources/test-project/target/classes/org/codefx/mvn/jdeps/testproject/OnUnsafe.class -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/ResultOutputStrategy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.apache.maven.plugin.MojoFailureException; 4 | 5 | public interface ResultOutputStrategy { 6 | 7 | void output(Result result) throws MojoFailureException; 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/test/resources/test-project/src/main/java/org/codefx/mvn/jdeps/testproject/OnUnsafe.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.testproject; 2 | 3 | import sun.misc.Unsafe; 4 | 5 | public class OnUnsafe { 6 | 7 | public static void main(String[] args) { 8 | Unsafe unsafe = null; 9 | unsafe.addressSize(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/test-project/src/main/java/org/codefx/mvn/jdeps/testproject/OnActions.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.testproject; 2 | 3 | import sun.security.action.GetBooleanAction; 4 | import sun.security.action.GetIntegerAction; 5 | 6 | public class OnActions { 7 | 8 | public static void main(String[] args) { 9 | GetBooleanAction booleanAction = new GetBooleanAction(""); 10 | GetIntegerAction integerAction = new GetIntegerAction(""); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mobile Tools for Java (J2ME) 2 | .mtj.tmp/ 3 | 4 | # Package Files # 5 | *.jar 6 | *.war 7 | *.ear 8 | 9 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 10 | hs_err_pid* 11 | 12 | # compilation folders 13 | /bin/ 14 | /out/ 15 | /target/ 16 | 17 | # Eclipse files and folders 18 | /.settings 19 | .classpath 20 | .project 21 | 22 | # IntelliJ folders 23 | /.idea 24 | /.idea-files 25 | *.iml 26 | 27 | # TestNG results 28 | /test-output 29 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/PackageInclusion.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | /** 4 | * Defines how packages are interpreted; see {@link DependencyJudge} for details. 5 | */ 6 | public enum PackageInclusion { 7 | 8 | /** 9 | * Packages are interpreted as officially specified, i.e. one can never contain another. 10 | */ 11 | FLAT, 12 | 13 | /** 14 | * Packages are interpreted like folders, i.e. packages can contain each other. 15 | */ 16 | HIERARCHICAL 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/test/resources/test-project/src/main/java/org/codefx/mvn/jdeps/testproject/OnBASE64.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.testproject; 2 | 3 | import sun.misc.BASE64Decoder; 4 | import sun.misc.BASE64Encoder; 5 | 6 | import java.io.IOException; 7 | 8 | public class OnBASE64 { 9 | 10 | public static void main(String[] args) throws IOException { 11 | BASE64Decoder decoder = null; 12 | decoder.decodeBuffer((String) null); 13 | 14 | BASE64Encoder encoder = null; 15 | encoder.encode((byte[]) null); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/JDepsSearch.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import java.nio.file.Path; 4 | import java.util.Optional; 5 | 6 | /** 7 | * Tries to locate the Java 8 | * Dependency Analysis Tool (jdeps). 9 | */ 10 | public interface JDepsSearch { 11 | 12 | /** 13 | * @return the path to the tool or an empty {@link Optional} if it was not found 14 | */ 15 | Optional search(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/Arrow.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import java.util.Arrays; 4 | 5 | import static java.util.stream.Collectors.joining; 6 | 7 | /** 8 | * The different visualisations of an arrow in arrow rules like {@code dependent -> dependency: severity}. 9 | */ 10 | public enum Arrow { 11 | 12 | ARROW("->"), 13 | ON("on"); 14 | 15 | /** 16 | * Returns a regular expression matching exactly the arrow'a texts. 17 | */ 18 | public static final String REGULAR_EXPRESSION_MATCHER = Arrays 19 | .stream(values()) 20 | .map(Arrow::text) 21 | .collect(joining("|", "(", ")")); 22 | 23 | private final String text; 24 | 25 | Arrow(String text) { 26 | this.text = text; 27 | } 28 | 29 | public String text() { 30 | return text; 31 | } 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/tool/jdeps/JavaHomeSystemPropertyJDepsSearchTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.junit.Test; 4 | 5 | import java.nio.file.Path; 6 | import java.util.Optional; 7 | 8 | /** 9 | * Tests {@link JavaHomeSystemPropertyJDepsSearch}. 10 | */ 11 | public class JavaHomeSystemPropertyJDepsSearchTest { 12 | 13 | /* 14 | * A real test is not possible without mocking the file system and system properties. 15 | */ 16 | 17 | @Test 18 | @SuppressWarnings("javadoc") 19 | public void search_stateUnknown_throwNoException() throws Exception { 20 | Optional jDeps = new JavaHomeSystemPropertyJDepsSearch().search(); 21 | // it is generally unknown whether this search can find jdeps so we can not assert anything 22 | System.out.println(jDeps); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/tool/jdeps/JavaHomeEnvironmentVariableJDepsSearchTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.junit.Test; 4 | 5 | import java.nio.file.Path; 6 | import java.util.Optional; 7 | 8 | /** 9 | * Tests {@link JavaHomeEnvironmentVariableJDepsSearch}. 10 | */ 11 | public class JavaHomeEnvironmentVariableJDepsSearchTest { 12 | 13 | /* 14 | * A real test is not possible without mocking the file system and system properties. 15 | */ 16 | 17 | @Test 18 | @SuppressWarnings("javadoc") 19 | public void search_stateUnknown_throwNoException() throws Exception { 20 | Optional jDeps = new JavaHomeEnvironmentVariableJDepsSearch().search(); 21 | // it is generally unknown whether this search can find jdeps so we can not assert anything 22 | System.out.println(jDeps); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/Severity.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import java.util.Arrays; 4 | import java.util.stream.Stream; 5 | 6 | /** 7 | * The severity of a dependency as defined by the configuration. 8 | */ 9 | public enum Severity { 10 | 11 | /** 12 | * Ignore individual dependencies. 13 | */ 14 | IGNORE, 15 | 16 | /** 17 | * Summarize these dependencies. 18 | */ 19 | SUMMARIZE, 20 | 21 | /** 22 | * Inform about individual dependencies. 23 | */ 24 | INFORM, 25 | 26 | /** 27 | * Warn about individual dependencies. 28 | */ 29 | WARN, 30 | 31 | /** 32 | * Fail the build for such dependencies. 33 | */ 34 | FAIL; 35 | 36 | /** 37 | * @return a stream of all severities 38 | */ 39 | public static Stream stream() { 40 | return Arrays.stream(values()); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/SimpleDependencyJudge.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | /** 6 | * A {@link DependencyJudge} that judges all dependencies with the severity specified during construction. 7 | */ 8 | public class SimpleDependencyJudge implements DependencyJudge { 9 | 10 | private final Severity severity; 11 | 12 | /** 13 | * Creates a new instance that judges all dependencies with the specified severity 14 | * 15 | * @param severity 16 | * the severity to use for all sependencies 17 | */ 18 | public SimpleDependencyJudge(Severity severity) { 19 | this.severity = requireNonNull(severity, "The argument 'severity' must not be null."); 20 | } 21 | 22 | @Override 23 | public Severity judgeSeverity(String dependentName, String dependencyName) { 24 | return severity; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/MavenToolchainJDepsSearch.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.apache.maven.toolchain.Toolchain; 4 | 5 | import java.nio.file.Path; 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | /** 10 | * Tries to locate JDeps in the JDK specified to the Maven Toolchain. 11 | */ 12 | class MavenToolchainJDepsSearch implements JDepsSearch { 13 | 14 | private final Toolchain toolchain; 15 | 16 | /** 17 | * Creates a new search using the specified toolchain. 18 | * 19 | * @param toolchain 20 | * the toolchain which will be used to locate JDeps 21 | */ 22 | public MavenToolchainJDepsSearch(Toolchain toolchain) { 23 | Objects.requireNonNull(toolchain, "The argument 'toolchain' must not be null."); 24 | this.toolchain = toolchain; 25 | } 26 | 27 | @Override 28 | public Optional search() { 29 | return Optional.empty(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/SystemOutResultOutputStrategy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.apache.maven.plugin.MojoFailureException; 4 | import org.codefx.mvn.jdeps.dependency.Violation; 5 | 6 | import java.util.stream.Stream; 7 | 8 | /** 9 | * A {@link ResultOutputStrategy} which simply prints to {@link System#out}. 10 | */ 11 | public class SystemOutResultOutputStrategy implements ResultOutputStrategy { 12 | 13 | @Override 14 | public void output(Result result) throws MojoFailureException { 15 | output("SUMMARIZE:", result.violationsToSummarize()); 16 | output("INFORM:", result.violationsToInform()); 17 | output("WARN:", result.violationsToWarn()); 18 | output("FAIL:", result.violationsToFail()); 19 | } 20 | 21 | private static void output(String header, Stream violations) { 22 | System.out.println("\n\n" + header + ":"); 23 | violations 24 | .flatMap(Violation::toLines) 25 | .forEach(System.out::println); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/ComposedJDepsSearch.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import java.nio.file.Path; 4 | import java.util.Optional; 5 | 6 | /** 7 | * Tries all available searches to locate JDeps. 8 | */ 9 | public class ComposedJDepsSearch implements JDepsSearch { 10 | 11 | private final JavaHomeSystemPropertyJDepsSearch systemPropertySearch; 12 | private final JavaHomeEnvironmentVariableJDepsSearch environmentVariableSearch; 13 | 14 | /** 15 | * Creates a new search. 16 | */ 17 | public ComposedJDepsSearch() { 18 | systemPropertySearch = new JavaHomeSystemPropertyJDepsSearch(); 19 | environmentVariableSearch = new JavaHomeEnvironmentVariableJDepsSearch(); 20 | } 21 | 22 | @Override 23 | public Optional search() { 24 | Optional viaSystemProperty = systemPropertySearch.search(); 25 | if (viaSystemProperty.isPresent()) 26 | return viaSystemProperty; 27 | 28 | Optional viaEnvironmentVariable = environmentVariableSearch.search(); 29 | if (viaEnvironmentVariable.isPresent()) 30 | return viaEnvironmentVariable; 31 | 32 | return Optional.empty(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/MapDependencyJudgeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.codefx.mvn.jdeps.rules.MapDependencyJudge.MapDependencyJudgeBuilder; 4 | import org.codefx.mvn.jdeps.rules.MapDependencyJudgeTest.AsFlat; 5 | import org.codefx.mvn.jdeps.rules.MapDependencyJudgeTest.AsHierarchical; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Suite; 8 | import org.junit.runners.Suite.SuiteClasses; 9 | 10 | /** 11 | * Tests for {@link MapDependencyJudge}. 12 | */ 13 | @RunWith(Suite.class) 14 | @SuiteClasses({AsFlat.class, AsHierarchical.class}) 15 | public class MapDependencyJudgeTest { 16 | 17 | public static class AsFlat extends AbstractFlatDependencyJudgeTest { 18 | 19 | @Override 20 | protected DependencyJudgeBuilder builder() { 21 | return new MapDependencyJudgeBuilder().withInclusion(PackageInclusion.FLAT); 22 | } 23 | 24 | } 25 | 26 | public static class AsHierarchical extends AbstractHierarchicalDependencyJudgeTest { 27 | 28 | @Override 29 | protected DependencyJudgeBuilder builder() { 30 | return new MapDependencyJudgeBuilder().withInclusion(PackageInclusion.HIERARCHICAL); 31 | } 32 | 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/SearchJDepsInJdk.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.apache.commons.lang3.SystemUtils; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.Optional; 8 | 9 | /** 10 | * Tries to locate JDeps inside the JDK folder. 11 | *

12 | * This class does not implement {@link JDepsSearch} because it can not function on its own and requires input (namely 13 | * the JDK folder). 14 | */ 15 | class SearchJDepsInJdk { 16 | 17 | /** 18 | * @param jdkHome 19 | * the path to the JDK in which JDeps will be searched for; does not have to be a valid directory (i.e. 20 | * could be a non-existent path or a file) 21 | * @return the path to JDeps if it could be found (i.e. a file with the correct name exists); otherwise an empty 22 | * {@link Optional} 23 | */ 24 | public Optional search(Path jdkHome) { 25 | Path jdkBin = jdkHome.resolve("bin"); 26 | Path jDeps = jdkBin.resolve(jDepsFileName()); 27 | 28 | return Files.isRegularFile(jDeps) 29 | ? Optional.of(jDeps) 30 | : Optional.empty(); 31 | } 32 | 33 | private static String jDepsFileName() { 34 | return "jdeps" + (SystemUtils.IS_OS_WINDOWS ? ".exe" : ""); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/JavaHomeSystemPropertyJDepsSearch.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.apache.commons.lang3.SystemUtils; 4 | 5 | import java.nio.file.Path; 6 | import java.util.Optional; 7 | 8 | /** 9 | * Tries to locate jdeps via the system property "java.home". 10 | */ 11 | final class JavaHomeSystemPropertyJDepsSearch implements JDepsSearch { 12 | 13 | private final SearchJDepsInJdk searchJDepsInJdk; 14 | 15 | /** 16 | * Creates a new search. 17 | */ 18 | public JavaHomeSystemPropertyJDepsSearch() { 19 | this(new SearchJDepsInJdk()); 20 | } 21 | 22 | /** 23 | * Creates a new search which uses the specified service to locate JDeps in the JDK folder. 24 | * 25 | * @param searchJDepsInJdk 26 | * used to locate JDeps in the JDK folder 27 | */ 28 | public JavaHomeSystemPropertyJDepsSearch(SearchJDepsInJdk searchJDepsInJdk) { 29 | this.searchJDepsInJdk = searchJDepsInJdk; 30 | } 31 | 32 | @Override 33 | public Optional search() { 34 | // "java.home" points to "jdk/jre" and jdeps can be found in "jdk/bin" (if this is run with a JDK) 35 | Path javaHome = SystemUtils.getJavaHome().toPath(); 36 | Path jdkHome = javaHome.getParent(); 37 | 38 | return searchJDepsInJdk.search(jdkHome); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/tool/jdeps/JdkInternalsExecutorTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import com.google.common.io.Resources; 4 | import org.junit.Ignore; 5 | import org.junit.Test; 6 | 7 | import java.nio.file.Path; 8 | import java.nio.file.Paths; 9 | 10 | /** 11 | * Tests {@link JdkInternalsExecutor}. 12 | *

13 | * This is no regular unit/integration test as it relies on correct configuration to pass. If this test fails simply 14 | * {@link Ignore} it or change the constants to reflect the situation on your system. 15 | */ 16 | //@Ignore 17 | @SuppressWarnings("javadoc") 18 | public class JdkInternalsExecutorTest { 19 | 20 | private static final Path PATH_TO_JDEPS = Paths.get("/opt/java/jdk8/bin/jdeps"); 21 | private static final Path PATH_TO_SCANNED_FOLDER; 22 | 23 | static { 24 | Path testProjectPom = Paths.get(Resources.getResource("test-project/pom.xml").getPath()); 25 | PATH_TO_SCANNED_FOLDER = testProjectPom.resolveSibling("target").resolve("classes"); 26 | } 27 | 28 | @Test 29 | public void execute_pathsExist_printsJDepsOutput() throws Exception { 30 | // print this class' name as a header for the following JDeps output 31 | System.out.println("\n# " + getClass().getSimpleName().toUpperCase() + "\n"); 32 | 33 | JdkInternalsExecutor executor = new JdkInternalsExecutor(PATH_TO_JDEPS, PATH_TO_SCANNED_FOLDER, System.out::println); 34 | executor.execute(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/ViolationsToRuleTransformer.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.codefx.mvn.jdeps.dependency.Violation; 4 | import org.codefx.mvn.jdeps.rules.DependencyRule; 5 | import org.codefx.mvn.jdeps.rules.Severity; 6 | 7 | import java.util.stream.Stream; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | /** 12 | * Converts a {@link Result}'s {@link Violation}s into {@link DependencyRule}s. 13 | */ 14 | public class ViolationsToRuleTransformer { 15 | 16 | /** 17 | * 18 | * @param result the result containing the violations 19 | * @return a stream of {@link DependencyRule}s 20 | */ 21 | public static Stream transform(Result result) { 22 | requireNonNull(result, "The argument 'result' must not be null."); 23 | return Severity.stream() 24 | .flatMap(severity -> dependencyRulesForSeverity(result, severity)); 25 | } 26 | 27 | private static Stream dependencyRulesForSeverity(Result result, Severity severity) { 28 | return result 29 | .violationsWithSeverity(severity) 30 | .flatMap(violation -> dependencyRulesForViolation(severity, violation)); 31 | } 32 | 33 | private static Stream dependencyRulesForViolation(Severity severity, Violation violation) { 34 | return violation 35 | .getInternalDependencies().stream() 36 | .map(dependency -> 37 | DependencyRule.of( 38 | violation.getDependent().getFullyQualifiedName(), 39 | dependency.getFullyQualifiedName(), 40 | severity)); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/AnnotatedInternalType.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.codefx.mvn.jdeps.dependency.InternalType; 4 | import org.codefx.mvn.jdeps.rules.Severity; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * An internal type which is annotated with a severity. 10 | *

11 | * Note that the same internal type (e.g. "sun.misc.Unsafe") might be annotated with different severities depending on 12 | * which class depends on it. Maybe {@code com.foo.Bar -> sun.misc.Unsafe} is {@link Severity#INFORM INFORM} but 13 | * {@code com.foo.Baz -> sun.misc.Unsafe} is {@link Severity#WARN}. 14 | */ 15 | final class AnnotatedInternalType { 16 | 17 | private final InternalType type; 18 | private final Severity severity; 19 | 20 | private AnnotatedInternalType(InternalType type, Severity severity) { 21 | this.type = requireNonNull(type, "The argument 'type' must not be null."); 22 | this.severity = requireNonNull(severity, "The argument 'severity' must not be null."); 23 | } 24 | 25 | /** 26 | * Returns an internal type annotated with the specified severity. 27 | * 28 | * @param type 29 | * the internal type to annotate 30 | * @param severity 31 | * the severity 32 | * 33 | * @return an annotated internal type 34 | */ 35 | public static AnnotatedInternalType of(InternalType type, Severity severity) { 36 | return new AnnotatedInternalType(type, severity); 37 | } 38 | 39 | public InternalType getType() { 40 | return type; 41 | } 42 | 43 | public Severity getSeverity() { 44 | return severity; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/JavaHomeEnvironmentVariableJDepsSearch.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.apache.commons.lang3.StringUtils; 4 | 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.Optional; 8 | 9 | /** 10 | * Tries to locate jdeps via the environment variable "JAVA_HOME". 11 | */ 12 | class JavaHomeEnvironmentVariableJDepsSearch implements JDepsSearch { 13 | 14 | private final SearchJDepsInJdk searchJDepsInJdk; 15 | 16 | /** 17 | * Creates a new search. 18 | */ 19 | public JavaHomeEnvironmentVariableJDepsSearch() { 20 | this(new SearchJDepsInJdk()); 21 | } 22 | 23 | /** 24 | * Creates a new search which uses the specified service to locate JDeps in the JDK folder. 25 | * 26 | * @param searchJDepsInJdk 27 | * used to locate JDeps in the JDK folder 28 | */ 29 | public JavaHomeEnvironmentVariableJDepsSearch(SearchJDepsInJdk searchJDepsInJdk) { 30 | this.searchJDepsInJdk = searchJDepsInJdk; 31 | } 32 | 33 | @Override 34 | public Optional search() { 35 | Optional javaHome = getJavaHome(); 36 | if (!javaHome.isPresent()) 37 | return Optional.empty(); 38 | 39 | // assume that "JAVA_HOME" points to a JDK (and not to a JRE); 40 | return searchJDepsInJdk.search(javaHome.get()); 41 | } 42 | 43 | private static Optional getJavaHome() { 44 | try { 45 | String javaHome = System.getenv("JAVA_HOME"); 46 | if (StringUtils.isEmpty(javaHome)) 47 | return Optional.empty(); 48 | return Optional.of(Paths.get(javaHome)); 49 | } catch (SecurityException ex) { 50 | return Optional.empty(); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/AbstractFlatDependencyJudgeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Abstract superclass for tests of hierarchical {@link DependencyJudge} implementations. 9 | */ 10 | public abstract class AbstractFlatDependencyJudgeTest extends AbstractDependencyJudgeTest { 11 | 12 | @Test 13 | public void judgeSeverity_dependentCoveredByRuleForSuperPackage_ruleIsNotApplied() { 14 | DependencyJudge judge = builder(). 15 | withDefaultSeverity(Severity.INFORM) 16 | .addDependency("com", "sun.misc.Unsafe", Severity.FAIL) 17 | .build(); 18 | 19 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 20 | assertThat(severity).isSameAs(Severity.INFORM); 21 | } 22 | 23 | @Test 24 | public void judgeSeverity_dependencyCoveredByRuleForSuperPackage_ruleIsNotApplied() { 25 | DependencyJudge judge = builder(). 26 | withDefaultSeverity(Severity.INFORM) 27 | .addDependency("com.foo.Bar", "sun", Severity.FAIL) 28 | .build(); 29 | 30 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 31 | assertThat(severity).isSameAs(Severity.INFORM); 32 | } 33 | 34 | @Test 35 | public void judgeSeverity_bothCoveredByRuleForSuperPackage_ruleIsNotApplied() { 36 | DependencyJudge judge = builder(). 37 | withDefaultSeverity(Severity.INFORM) 38 | .addDependency("com", "sun", Severity.FAIL) 39 | .build(); 40 | 41 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 42 | assertThat(severity).isSameAs(Severity.INFORM); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/AbstractHierarchicalDependencyJudgeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Abstract superclass for tests of hierarchical {@link DependencyJudge} implementations. 9 | */ 10 | public abstract class AbstractHierarchicalDependencyJudgeTest extends AbstractDependencyJudgeTest { 11 | 12 | @Test 13 | public void judgeSeverity_dependentCoveredByRuleForSuperPackage_ruleIsApplied() { 14 | DependencyJudge judge = builder(). 15 | withDefaultSeverity(Severity.INFORM) 16 | .addDependency("com", "sun.misc.Unsafe", Severity.FAIL) 17 | .build(); 18 | 19 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 20 | assertThat(severity).isSameAs(Severity.FAIL); 21 | } 22 | 23 | @Test 24 | public void judgeSeverity_dependencyCoveredByRuleForSuperPackage_ruleIsApplied() { 25 | DependencyJudge judge = builder(). 26 | withDefaultSeverity(Severity.INFORM) 27 | .addDependency("com.foo.Bar", "sun", Severity.FAIL) 28 | .build(); 29 | 30 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 31 | assertThat(severity).isSameAs(Severity.FAIL); 32 | } 33 | 34 | @Test 35 | public void judgeSeverity_bothCoveredByRuleForSuperPackage_ruleIsApplied() { 36 | DependencyJudge judge = builder(). 37 | withDefaultSeverity(Severity.INFORM) 38 | .addDependency("com", "sun", Severity.FAIL) 39 | .build(); 40 | 41 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 42 | assertThat(severity).isSameAs(Severity.FAIL); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/XmlRuleTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.assertj.core.api.Condition; 4 | import org.junit.Test; 5 | 6 | import java.util.List; 7 | 8 | import static java.util.stream.Collectors.toList; 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class XmlRuleTest { 12 | 13 | private XmlRule rule = new XmlRule("dependent", "depenendency", Severity.FAIL); 14 | 15 | @Test(expected = NullPointerException.class) 16 | public void toXmlLines_indentNull_throwsException() { 17 | rule.toXmlLines(null); 18 | } 19 | 20 | @Test(expected = IllegalArgumentException.class) 21 | public void toXmlLines_indentNotEmpty_throwsException() { 22 | rule.toXmlLines("x"); 23 | } 24 | 25 | @Test 26 | public void toXmlLines_validIndent_indentIsUsed() throws Exception { 27 | List lines = rule.toXmlLines("\t").collect(toList()); 28 | 29 | Condition startingWithIndentUnlessOuterTagLines = new Condition<>( 30 | line -> line.startsWith("\t") || line.equals("") || line.equals(""), 31 | "Start with indent."); 32 | assertThat(lines).are(startingWithIndentUnlessOuterTagLines); 33 | } 34 | 35 | @Test 36 | public void toXmlLines_validRule_containsAllInformation() throws Exception { 37 | XmlRule rule = new XmlRule("DEPENDENT", "DEPENDENCY", Severity.FAIL); 38 | List lines = rule.toXmlLines("").collect(toList()); 39 | 40 | assertThat(lines).containsExactly( 41 | "", 42 | "DEPENDENT", 43 | "DEPENDENCY", 44 | "FAIL", 45 | ""); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/FailBuildResultOutputStrategy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.apache.maven.plugin.MojoFailureException; 4 | import org.codefx.mvn.jdeps.dependency.Violation; 5 | import org.codefx.mvn.jdeps.tool.PairCollector.Pair; 6 | 7 | import java.util.stream.Stream; 8 | 9 | import static java.lang.String.format; 10 | import static java.util.stream.Collectors.joining; 11 | import static java.util.stream.Collectors.reducing; 12 | import static java.util.stream.Collectors.summingInt; 13 | import static org.codefx.mvn.jdeps.tool.PairCollector.pairing; 14 | 15 | /** 16 | * A {@link ResultOutputStrategy} that fails the build if the result contains violations that are configured to do so. 17 | */ 18 | public class FailBuildResultOutputStrategy implements ResultOutputStrategy { 19 | 20 | static final String MESSAGE_FAIL_DEPENDENCIES = 21 | LogResultOutputStrategy.MESSAGE_ABOUT_JDEPS + "\nConfigured to FAIL are %1$s:\n%2$s"; 22 | 23 | @Override 24 | public void output(Result result) throws MojoFailureException { 25 | Pair> countAndMessage = result 26 | .violationsToFail() 27 | .collect(pairing( 28 | summingInt(violation -> violation.getInternalDependencies().size()), 29 | reducing(Stream.of(), Violation::toLines, Stream::concat))); 30 | 31 | if (countAndMessage.first > 0) 32 | throw new MojoFailureException(format( 33 | MESSAGE_FAIL_DEPENDENCIES, 34 | countAndMessage.first, 35 | // whitespace at the lines' beginnings are apparently removed by Maven so prefix with a dot 36 | countAndMessage.second.map(line -> "." + line).collect(joining("\n")))); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/mojo/MojoLogging.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import org.apache.maven.plugin.AbstractMojo; 4 | import org.apache.maven.plugin.logging.Log; 5 | import org.apache.maven.plugin.logging.SystemStreamLog; 6 | 7 | import java.util.Optional; 8 | import java.util.function.Supplier; 9 | 10 | /** 11 | * Evil global-state that makes the {@link JdkInternalsMojo mojo}'s logger available to all the plugin's classes. 12 | *

13 | * This class manages a function that can return a logger because {@link AbstractMojo#getLog()} (the method that will 14 | * likely be used to access the logger) states: "simply call this method directly whenever you need the logger". 15 | */ 16 | public class MojoLogging { 17 | 18 | private static final Log fallbackSystemStreamLog = new SystemStreamLog(); 19 | 20 | private static Optional> getLogger = Optional.empty(); 21 | 22 | /** 23 | * @return the currently registered logger 24 | */ 25 | public static Log logger() { 26 | return getLogger 27 | .orElse(() -> fallbackSystemStreamLog) 28 | .get(); 29 | } 30 | 31 | /** 32 | * Registers the specified function to access the logger from here on. 33 | * 34 | * @param getLogger 35 | * a supplier for the logger 36 | */ 37 | static void registerLogger(Supplier getLogger) { 38 | MojoLogging.getLogger = Optional.of(getLogger); 39 | } 40 | 41 | /** 42 | * Unregisters the last registered logger. 43 | *

44 | * Until another logger is {@link #registerLogger(Supplier) registered}, a {@link SystemStreamLog} will be used. 45 | */ 46 | static void unregisterLogger() { 47 | getLogger = Optional.empty(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/DependencyJudgeBuilder.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | /** 4 | * Builds a {@link DependencyJudge}. 5 | */ 6 | public interface DependencyJudgeBuilder { 7 | 8 | /** 9 | * Sets the specified package inclusion. 10 | *

11 | * Default value is {@link PackageInclusion#FLAT}. 12 | * 13 | * @param packageInclusion 14 | * the package inclusion to set 15 | * 16 | * @return this builder 17 | */ 18 | DependencyJudgeBuilder withInclusion(PackageInclusion packageInclusion); 19 | 20 | /** 21 | * Sets the specified severity as the default severity used by the created judge. 22 | *

23 | * Default value is {@link Severity#WARN}. 24 | * 25 | * @param defaultSeverity 26 | * the default severity to set 27 | * 28 | * @return this builder 29 | */ 30 | DependencyJudgeBuilder withDefaultSeverity(Severity defaultSeverity); 31 | 32 | /** 33 | * Adds the specified dependency rule to the created judge. 34 | * 35 | * @param dependentName 36 | * fully qualified name of the type or package which depends on the the other 37 | * @param dependencyName 38 | * fully qualified name of the type or package upon which the {@code dependent} depends 39 | * @param severity 40 | * the severity of the dependency {@code dependent -> dependency} 41 | * 42 | * @return this builder 43 | */ 44 | default DependencyJudgeBuilder addDependency(String dependentName, String dependencyName, Severity severity) { 45 | return addDependency(DependencyRule.of(dependentName, dependencyName, severity)); 46 | } 47 | 48 | /** 49 | * Adds the specified dependency rule to the created judge. 50 | * 51 | * @param rule 52 | * the rule to add 53 | * 54 | * @return this builder 55 | */ 56 | DependencyJudgeBuilder addDependency(DependencyRule rule); 57 | 58 | /** 59 | * @return a new dependency judge 60 | * 61 | * @throws IllegalStateException 62 | * if called more than once 63 | */ 64 | DependencyJudge build(); 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/Factory.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps; 2 | 3 | import org.codefx.mvn.jdeps.dependency.InternalType; 4 | import org.codefx.mvn.jdeps.dependency.Type; 5 | import org.codefx.mvn.jdeps.dependency.Violation; 6 | import org.codefx.mvn.jdeps.dependency.Violation.ViolationBuilder; 7 | 8 | import java.util.Arrays; 9 | 10 | /** 11 | * Factory methods that can be shared across different tests. 12 | */ 13 | public class Factory { 14 | 15 | /** 16 | * @param dependent 17 | * the fully qualified name of the dependent 18 | * @param dependencies 19 | * a variable number of dependencies 20 | * 21 | * @return a violation 22 | */ 23 | public static Violation violation(String dependent, String... dependencies) { 24 | ViolationBuilder violationBuilder = Violation.buildForDependent(Type.of(dependent)); 25 | Arrays.stream(dependencies) 26 | // 'InternalType.of' requires the fully qualified name to be split into package and class name; 27 | // to not write such code here, create a 'Type' from the fully qualified name, first 28 | .map(Type::of) 29 | .map(type -> InternalType.of(type.getPackageName(), type.getClassName(), "", "")) 30 | .forEachOrdered(violationBuilder::addDependency); 31 | return violationBuilder.build(); 32 | } 33 | 34 | /** 35 | * @return the violation in {@code OnActions} 36 | */ 37 | public static Violation onActionsViolation() { 38 | return violation( 39 | "org.codefx.mvn.jdeps.testproject.OnActions", 40 | "sun.security.action.GetBooleanAction", "sun.security.action.GetIntegerAction"); 41 | 42 | } 43 | 44 | /** 45 | * @return the violation in {@code OnBASE64} 46 | */ 47 | public static Violation onBASE64Violation() { 48 | return violation( 49 | "org.codefx.mvn.jdeps.testproject.OnBASE64", 50 | "sun.misc.BASE64Decoder", "sun.misc.BASE64Encoder"); 51 | 52 | } 53 | 54 | /** 55 | * @return the violation in {@code OnUnsafe} 56 | */ 57 | public static Violation onUnsafeViolation() { 58 | return violation("org.codefx.mvn.jdeps.testproject.OnUnsafe", "sun.misc.Unsafe"); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/dependency/InternalType.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.dependency; 2 | 3 | import static java.util.Objects.requireNonNull; 4 | 5 | /** 6 | * A type which is considered JDK-internal API by JDeps. 7 | *

8 | * Besides the type's package and class name, an internal type also contains the category (e.g. "JDK internal API") and 9 | * source which contains the type (e.g. "rt.jar") as reported by JDeps. 10 | */ 11 | public final class InternalType extends Type { 12 | 13 | private final String category; 14 | private final String source; 15 | 16 | private InternalType(String packageName, String className, String category, String source) { 17 | super(packageName, className); 18 | this.category = requireNonNull(category, "The argument 'category' must not be null."); 19 | this.source = requireNonNull(source, "The argument 'source' must not be null."); 20 | } 21 | 22 | /** 23 | * Returns an internal type for the specified arguments. 24 | * 25 | * @param packageName 26 | * the name of the package containing the type (dotted) 27 | * @param className 28 | * the name of the type's class (dotted) 29 | * @param category 30 | * the category as reported by JDeps (e.g. "JDK internal API") 31 | * @param source 32 | * the source as reported by JDeps (e.g. "rt.jar") 33 | * 34 | * @return an internal type 35 | */ 36 | public static InternalType of( 37 | String packageName, 38 | String className, 39 | String category, 40 | String source) { 41 | return new InternalType(packageName, className, category, source); 42 | } 43 | 44 | /** 45 | * @return the category of this internal dependency as reported by JDeps (e.g. "JDK internal API") 46 | */ 47 | public String getCategory() { 48 | return category; 49 | } 50 | 51 | /** 52 | * @return the source of this internal dependency as reported by JDeps (e.g. "rt.jar") 53 | */ 54 | public String getSource() { 55 | return source; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return super.toString() + " [" + category + ", " + source + "]"; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/resources/test-project/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.codefx.mvn 6 | jdeps-maven-plugin-test 7 | 1-SNAPSHOT 8 | jar 9 | 10 | JDepsMavenPlugin-Test 11 | 12 | 13 | UTF-8 14 | 15 | 16 | 17 | 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-compiler-plugin 22 | 3.3 23 | 24 | 1.8 25 | 1.8 26 | 27 | 28 | 29 | org.codefx.mvn 30 | jdeps-maven-plugin 31 | 0.2-SNAPSHOT 32 | 33 | 34 | 35 | WARN 36 | HIERARCHICAL 37 | 38 | 39 | org.codefx.mvn.jdeps.testproject 40 | sun.misc.BASE64Encoder 41 | INFORM 42 | 43 | 44 | 45 | 46 | org.codefx.mvn.jdeps.testproject -> sun.security: SUMMARIZE 47 | org.codefx.mvn.jdeps.testproject -> sun.misc.Unsafe: FAIL 48 | 49 | 50 | false 51 | ARROW 52 | 53 | 54 | jdkinternals 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/Result.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.codefx.mvn.jdeps.dependency.Violation; 5 | import org.codefx.mvn.jdeps.rules.Severity; 6 | 7 | import java.util.Optional; 8 | import java.util.stream.Stream; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * The result of running JDeps. 14 | *

15 | * The violations are made available with a number streams, one for each severity. 16 | */ 17 | public class Result { 18 | 19 | private final ImmutableList violations; 20 | 21 | Result(ImmutableList violations) { 22 | this.violations = requireNonNull(violations, "The argument 'violations' must not be null."); 23 | } 24 | 25 | /** 26 | * @param severity 27 | * the severity to filter by 28 | * 29 | * @return a stream of violations with the specified severity 30 | */ 31 | public Stream violationsWithSeverity(Severity severity) { 32 | return violations.stream() 33 | .map(violation -> violation.only(severity)) 34 | .filter(Optional::isPresent) 35 | .map(Optional::get); 36 | } 37 | 38 | /** 39 | * @return a stream of the violations that are configured to be be ignored 40 | */ 41 | public Stream violationsToIgnore() { 42 | return violationsWithSeverity(Severity.IGNORE); 43 | } 44 | 45 | /** 46 | * @return a stream of the violations that are configured to be be summarized 47 | */ 48 | public Stream violationsToSummarize() { 49 | return violationsWithSeverity(Severity.SUMMARIZE); 50 | } 51 | 52 | /** 53 | * @return a stream of the violations that are configured to be be informed about 54 | */ 55 | public Stream violationsToInform() { 56 | return violationsWithSeverity(Severity.INFORM); 57 | } 58 | 59 | /** 60 | * @return a stream of the violations that are configured to be warned about 61 | */ 62 | public Stream violationsToWarn() { 63 | return violationsWithSeverity(Severity.WARN); 64 | } 65 | 66 | /** 67 | * @return a stream of the violations that are configured to fail the build 68 | */ 69 | public Stream violationsToFail() { 70 | return violationsWithSeverity(Severity.FAIL); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/ResultBuilder.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.codefx.mvn.jdeps.dependency.InternalType; 5 | import org.codefx.mvn.jdeps.dependency.Type; 6 | import org.codefx.mvn.jdeps.dependency.Violation; 7 | import org.codefx.mvn.jdeps.rules.DependencyJudge; 8 | import org.codefx.mvn.jdeps.rules.Severity; 9 | 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * Builds a result, judging the violation's severities with a {@link DependencyJudge} specified during construction. 14 | *

15 | * Builder instances can be reused; it is safe to call {@link #build()} multiple times to build multiple lists in 16 | * series. Each new list contains all the elements of the ones created before it. 17 | */ 18 | public class ResultBuilder { 19 | 20 | private final DependencyJudge judge; 21 | private final ImmutableList.Builder violations; 22 | 23 | /** 24 | * Creates a new result builder. 25 | * 26 | * @param judge 27 | * the dependency judge to use 28 | */ 29 | public ResultBuilder(DependencyJudge judge) { 30 | this.judge = requireNonNull(judge, "The argument 'judge' must not be null."); 31 | violations = ImmutableList.builder(); 32 | } 33 | 34 | /** 35 | * Adds the specified violation to the result currently being built. 36 | * 37 | * @param violation 38 | * the violation to add 39 | */ 40 | public ResultBuilder addViolation(Violation violation) { 41 | Type dependent = violation.getDependent(); 42 | ImmutableList.Builder internalDependencies = ImmutableList.builder(); 43 | violation 44 | .getInternalDependencies().stream() 45 | .map(dependency -> annotateWithSeverity(dependent, dependency)) 46 | .forEach(internalDependencies::add); 47 | violations.add(AnnotatedViolation.of(dependent, internalDependencies.build())); 48 | 49 | return this; 50 | } 51 | 52 | private AnnotatedInternalType annotateWithSeverity(Type dependent, InternalType dependency) { 53 | Severity severity = judge.judgeSeverity(dependent, dependency); 54 | return AnnotatedInternalType.of(dependency, severity); 55 | } 56 | 57 | /** 58 | * Builds a new result. 59 | *

60 | * Can be called repeatedly, each call creating a new result containing all the violations added since this builder 61 | * was created. 62 | * 63 | * @return a new result 64 | */ 65 | public Result build() { 66 | return new Result(violations.build()); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/mojo/JdkInternalsExecutionServiceTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import com.google.common.io.Resources; 4 | import org.assertj.core.api.Assertions; 5 | import org.codefx.mvn.jdeps.dependency.Violation; 6 | import org.codefx.mvn.jdeps.result.Result; 7 | import org.codefx.mvn.jdeps.rules.PackageInclusion; 8 | import org.codefx.mvn.jdeps.rules.Severity; 9 | import org.junit.Test; 10 | 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | import static java.util.stream.Collectors.toList; 17 | import static org.codefx.mvn.jdeps.Factory.onActionsViolation; 18 | import static org.codefx.mvn.jdeps.Factory.onBASE64Violation; 19 | import static org.codefx.mvn.jdeps.Factory.onUnsafeViolation; 20 | 21 | /** 22 | * Integration tests of {@link JdkInternalsExecutionService}. 23 | *

24 | * This test can only pass if {@code test(resources/test-project/target/classes} contains compiled classes. 25 | */ 26 | public class JdkInternalsExecutionServiceTest { 27 | 28 | private static final Path PATH_TO_SCANNED_FOLDER; 29 | 30 | static { 31 | Path testProjectPom = Paths.get(Resources.getResource("test-project/pom.xml").getPath()); 32 | PATH_TO_SCANNED_FOLDER = testProjectPom.resolveSibling("target").resolve("classes"); 33 | } 34 | 35 | @Test 36 | public void execute_internalDependenciesExist_returnsViolations() throws Exception { 37 | // print this class' name as a header for the following JDeps output 38 | System.out.println("\n# " + getClass().getSimpleName().toUpperCase() + "\n"); 39 | 40 | Result result = JdkInternalsExecutionService.execute( 41 | PATH_TO_SCANNED_FOLDER, 42 | new DependencyRulesConfiguration( 43 | Severity.WARN, PackageInclusion.HIERARCHICAL, 44 | Collections.emptyList(), Collections.emptyList())); 45 | 46 | Assertions.assertThat(violations(result, Severity.IGNORE)).isEmpty(); 47 | Assertions.assertThat(violations(result, Severity.SUMMARIZE)).isEmpty(); 48 | Assertions.assertThat(violations(result, Severity.INFORM)).isEmpty(); 49 | Assertions.assertThat(violations(result, Severity.WARN)).containsOnly( 50 | onActionsViolation(), 51 | onBASE64Violation(), 52 | onUnsafeViolation() 53 | ); 54 | Assertions.assertThat(violations(result, Severity.FAIL)).isEmpty(); 55 | } 56 | 57 | private static List violations(Result result, Severity severity) { 58 | return result.violationsWithSeverity(severity).collect(toList()); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/mojo/JdkInternalsExecutionService.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import org.codefx.mvn.jdeps.parse.ViolationParser; 4 | import org.codefx.mvn.jdeps.result.Result; 5 | import org.codefx.mvn.jdeps.result.ResultBuilder; 6 | import org.codefx.mvn.jdeps.rules.DependencyJudge; 7 | import org.codefx.mvn.jdeps.tool.jdeps.ComposedJDepsSearch; 8 | import org.codefx.mvn.jdeps.tool.jdeps.JDepsSearch; 9 | import org.codefx.mvn.jdeps.tool.jdeps.JdkInternalsExecutor; 10 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 11 | import org.codehaus.plexus.util.cli.CommandLineException; 12 | 13 | import java.nio.file.Path; 14 | 15 | /** 16 | * Orchestrates all bits and pieces which are needed to run "jdeps -jdkInternals" and parse the output. 17 | */ 18 | class JdkInternalsExecutionService { 19 | 20 | /** 21 | * Executes jdeps. 22 | * 23 | * @param scannedFolder 24 | * the folder to be scanned by JDeps 25 | * @param dependencyRulesConfiguration 26 | * the configuration for the dependency rules 27 | * 28 | * @throws CommandLineException 29 | * if the jdeps executable could not be found, running the tool failed or it returned with an error 30 | */ 31 | public static Result execute(Path scannedFolder, DependencyRulesConfiguration dependencyRulesConfiguration) 32 | throws CommandLineException, ConfigurationException { 33 | 34 | ResultBuilder resultBuilder = createResultBuilder(dependencyRulesConfiguration); 35 | createJdkInternalsExecutor(scannedFolder, resultBuilder).execute(); 36 | return resultBuilder.build(); 37 | } 38 | 39 | private static ResultBuilder createResultBuilder(DependencyRulesConfiguration dependencyRulesConfiguration) 40 | throws ConfigurationException { 41 | DependencyJudge dependencyJudge = dependencyRulesConfiguration.createJudge(); 42 | return new ResultBuilder(dependencyJudge); 43 | } 44 | 45 | private static JdkInternalsExecutor createJdkInternalsExecutor(Path scannedFolder, ResultBuilder resultBuilder) 46 | throws CommandLineException { 47 | Path jDepsExecutable = findJDepsExecutable(); 48 | ViolationParser violationParser = new ViolationParser(resultBuilder::addViolation); 49 | return new JdkInternalsExecutor(jDepsExecutable, scannedFolder, violationParser::parseLine); 50 | } 51 | 52 | private static Path findJDepsExecutable() throws CommandLineException { 53 | JDepsSearch jDepsSearch = new ComposedJDepsSearch(); 54 | return jDepsSearch.search().orElseThrow(() -> new CommandLineException("Could not locate JDeps executable.")); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/RuleOutputStrategy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.apache.maven.plugin.MojoFailureException; 4 | import org.codefx.mvn.jdeps.rules.DependencyRule; 5 | 6 | import java.io.IOException; 7 | import java.util.function.Function; 8 | import java.util.stream.Stream; 9 | 10 | import static java.util.Comparator.comparing; 11 | import static java.util.Objects.requireNonNull; 12 | 13 | /** 14 | * Interprets a result's violations as dependency rules and writes them to a file. 15 | */ 16 | public class RuleOutputStrategy implements ResultOutputStrategy { 17 | 18 | private final Function> getRulesFromResult; 19 | private final Function> convertRuleToLines; 20 | private final Writer writer; 21 | 22 | /** 23 | * Creates a new output strategy, relying on the specified functions to do most of the work. 24 | * 25 | * @param getRulesFromResult 26 | * transforms a {@link Result} to a stream of {@link DependencyRule}s 27 | * @param convertRuleToLines 28 | * transforms dependency rules to lines 29 | * @param writer 30 | * writes lines to a file 31 | */ 32 | public RuleOutputStrategy( 33 | Function> getRulesFromResult, 34 | Function> convertRuleToLines, 35 | Writer writer) { 36 | this.getRulesFromResult = 37 | requireNonNull(getRulesFromResult, "The argument 'getRulesFromResult' must not be null."); 38 | this.convertRuleToLines = 39 | requireNonNull(convertRuleToLines, "The argument 'convertRuleToLines' must not be null."); 40 | this.writer = requireNonNull(writer, "The argument 'writer' must not be null."); 41 | } 42 | 43 | @Override 44 | public void output(Result result) throws MojoFailureException { 45 | Stream lines = getDependencyRuleLines(result); 46 | writeDependencyRuleLines(lines); 47 | } 48 | 49 | private Stream getDependencyRuleLines(Result result) { 50 | return getRulesFromResult 51 | .apply(result) 52 | .sorted(comparing(DependencyRule::getDependent).thenComparing(DependencyRule::getSeverity)) 53 | .flatMap(convertRuleToLines); 54 | } 55 | 56 | private void writeDependencyRuleLines(Stream dependencyRuleLines) throws MojoFailureException { 57 | try { 58 | writer.write(dependencyRuleLines); 59 | } catch (IOException ex) { 60 | throw new MojoFailureException(ex.getMessage(), ex.getCause()); 61 | } 62 | } 63 | 64 | /** 65 | * Writes a stream of lines to a file. 66 | */ 67 | public interface Writer { 68 | 69 | void write(Stream lines) throws IOException; 70 | 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/parse/InternalTypeLineParser.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.parse; 2 | 3 | import org.codefx.mvn.jdeps.dependency.InternalType; 4 | 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Parses a single line of JDeps output to an {@link InternalType}. 12 | *

13 | * Such lines must generally be of the following form: 14 | * 15 | *

16 |  *       -> package.name.ClassName     category (source)
17 |  * 
18 | */ 19 | class InternalTypeLineParser { 20 | 21 | private static final Pattern JDEPS_DEPENDENCY_PATTERN = Pattern.compile("" 22 | + "\\s+->\\s+" // leading spaces and arrow, e.g. " -> " 23 | + "([a-zA-Z_][\\.\\w]*)" // qualified class name (simplified), e.g. "sun.misc.Unsafe" 24 | + "\\s+" // spaces to fill up the column 25 | + "(\\w[\\w\\s]*\\w*)" // category, e.g. "JDK Internal API" 26 | + "\\s" // space between category and source 27 | + "\\(([\\w\\.]*)\\)" // source, e.g. "(jt.jar)" 28 | + ".*"); 29 | 30 | /** 31 | * Indicates whether the specified line is an {@link InternalType}. 32 | * 33 | * @param line 34 | * the line to check 35 | * @return true if the line can be parsed to an {@link InternalType} 36 | */ 37 | public boolean isInternalTypeLine(String line) { 38 | Objects.requireNonNull(line, "The argument 'line' must not be null."); 39 | 40 | return JDEPS_DEPENDENCY_PATTERN.matcher(line).matches(); 41 | } 42 | 43 | /** 44 | * Tries to parse the specified line to an {@link InternalType}. 45 | * 46 | * @param line 47 | * the line to parse 48 | * @return an {@link InternalType} if the line could be parsed; otherwise an empty {@link Optional} 49 | */ 50 | public Optional parseLine(String line) { 51 | Objects.requireNonNull(line, "The argument 'line' must not be null."); 52 | 53 | Matcher lineMatcher = JDEPS_DEPENDENCY_PATTERN.matcher(line); 54 | if (!lineMatcher.matches() || lineMatcher.groupCount() != 3) 55 | return Optional.empty(); 56 | 57 | String fullyQualifiedClassName = lineMatcher.group(1); 58 | String category = lineMatcher.group(2); 59 | String source = lineMatcher.group(3); 60 | 61 | InternalType type = InternalType.of( 62 | extractPackageName(fullyQualifiedClassName), 63 | extractClassName(fullyQualifiedClassName), 64 | category, 65 | source); 66 | return Optional.of(type); 67 | } 68 | 69 | private static String extractPackageName(String fullyQualifiedClassName) { 70 | int indexOfLastPoint = fullyQualifiedClassName.lastIndexOf('.'); 71 | return fullyQualifiedClassName.substring(0, indexOfLastPoint); 72 | } 73 | 74 | private static String extractClassName(String fullyQualifiedClassName) { 75 | int indexOfLastPoint = fullyQualifiedClassName.lastIndexOf('.'); 76 | return fullyQualifiedClassName.substring(indexOfLastPoint + 1); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/RuleOutputFormat.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.codefx.mvn.jdeps.rules.Arrow; 5 | import org.codefx.mvn.jdeps.rules.ArrowRuleParser; 6 | import org.codefx.mvn.jdeps.rules.DependencyRule; 7 | import org.codefx.mvn.jdeps.rules.XmlRule; 8 | import org.codefx.mvn.jdeps.tool.LineWriter.StaticContent; 9 | 10 | import java.util.function.Function; 11 | import java.util.stream.Stream; 12 | 13 | import static java.lang.String.format; 14 | import static java.util.Objects.requireNonNull; 15 | 16 | /** 17 | * Enumerates the ways in which rules can be output. 18 | */ 19 | public enum RuleOutputFormat { 20 | 21 | /** 22 | * Full XML; see {@link XmlRule} 23 | */ 24 | XML, 25 | 26 | /** 27 | * Single line rule {@code dependent -> dependency: severity}; see {@link ArrowRuleParser}. 28 | */ 29 | ARROW, 30 | 31 | /** 32 | * Single line rule {@code dependent on dependency: severity}; see {@link ArrowRuleParser}. 33 | */ 34 | ON; 35 | 36 | /** 37 | * @param indent 38 | * the string used to indent inner XML, possibly {@code "\t"} 39 | * 40 | * @return the static content for this output format 41 | */ 42 | public StaticContent getStaticContent(String indent) { 43 | requireNonNull(indent, "The argument 'indent' must not be null."); 44 | if (!indent.trim().isEmpty()) 45 | throw new IllegalArgumentException("The argument 'indent' must only consist of whitespace."); 46 | 47 | switch (this) { 48 | case ARROW: 49 | // intended fall through: both cases have the same prolog and epilog 50 | case ON: 51 | return new StaticContent( 52 | ImmutableList.of("", indent + ""), 53 | ImmutableList.of(indent + "", ""), 54 | indent + indent); 55 | case XML: 56 | return new StaticContent( 57 | ImmutableList.of(""), 58 | ImmutableList.of(""), 59 | indent); 60 | default: 61 | throw new IllegalArgumentException(format("Unknown format '%s'.", this)); 62 | } 63 | } 64 | 65 | /** 66 | * Creates a function which transforms dependency rules to parsable lines that can be written to a file. 67 | * 68 | * @param staticContent 69 | * the static content used for this format 70 | * 71 | * @return a function mapping a dependency rule to a stream of lines 72 | */ 73 | public Function> getToLinesTransformer(StaticContent staticContent) { 74 | requireNonNull(staticContent, "The argument 'staticContent' must not be null."); 75 | 76 | switch (this) { 77 | case ARROW: 78 | return rule -> Stream.of(ArrowRuleParser.ruleToArrowString(Arrow.ARROW, rule)); 79 | case ON: 80 | return rule -> Stream.of(ArrowRuleParser.ruleToArrowString(Arrow.ON, rule)); 81 | case XML: 82 | return rule -> new XmlRule(rule).toXmlLines(staticContent.indent); 83 | default: 84 | throw new IllegalArgumentException(format("Unknown format '%s'.", this)); 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/mojo/OutputConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import org.codefx.mvn.jdeps.result.FailBuildResultOutputStrategy; 4 | import org.codefx.mvn.jdeps.result.LogResultOutputStrategy; 5 | import org.codefx.mvn.jdeps.result.ResultOutputStrategy; 6 | import org.codefx.mvn.jdeps.result.RuleOutputFormat; 7 | import org.codefx.mvn.jdeps.result.RuleOutputStrategy; 8 | import org.codefx.mvn.jdeps.result.RuleOutputStrategy.Writer; 9 | import org.codefx.mvn.jdeps.result.ViolationsToRuleTransformer; 10 | import org.codefx.mvn.jdeps.tool.LineWriter; 11 | import org.codefx.mvn.jdeps.tool.LineWriter.IfFileExists; 12 | import org.codefx.mvn.jdeps.tool.LineWriter.StaticContent; 13 | 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.nio.file.Paths; 17 | 18 | import static java.util.Objects.requireNonNull; 19 | import static org.codefx.mvn.jdeps.mojo.MojoLogging.logger; 20 | 21 | class OutputConfiguration { 22 | 23 | private static final String DEFAULT_FILE_NAME = "dependency_rules.xml"; 24 | private static final String DEFAULT_INDENT = "\t"; 25 | 26 | private final boolean outputRules; 27 | private final RuleOutputFormat format; 28 | private final String filePath; 29 | 30 | public OutputConfiguration( 31 | boolean outputRules, RuleOutputFormat format, String filePath) { 32 | this.outputRules = requireNonNull(outputRules, "The argument 'outputRules' must not be null."); 33 | this.format = requireNonNull(format, "The argument 'format' must not be null."); 34 | this.filePath = requireNonNull(filePath, "The argument 'filePath' must not be null."); 35 | } 36 | 37 | public ResultOutputStrategy createOutputStrategy() { 38 | LogResultOutputStrategy logResult = new LogResultOutputStrategy(); 39 | ResultOutputStrategy outputRulesOrFailBuild = 40 | outputRules ? createRuleOutputStrategy() : createFailingStrategy(); 41 | 42 | // always log the result before doing anything else 43 | return result -> { 44 | logResult.output(result); 45 | outputRulesOrFailBuild.output(result); 46 | }; 47 | } 48 | 49 | private ResultOutputStrategy createRuleOutputStrategy() { 50 | StaticContent outputFormatStaticContent = format.getStaticContent(DEFAULT_INDENT); 51 | return new RuleOutputStrategy( 52 | ViolationsToRuleTransformer::transform, 53 | format.getToLinesTransformer(outputFormatStaticContent), 54 | createLineWriter(outputFormatStaticContent)); 55 | } 56 | 57 | private Writer createLineWriter(StaticContent outputFormatStaticContent) { 58 | Path file = getFile(filePath); 59 | LineWriter lineWriter = new LineWriter(file, IfFileExists.APPEND_NEW_CONTENT, outputFormatStaticContent); 60 | return lines -> { 61 | logger().debug(String.format("Starting to write rules to '%s' ...", file)); 62 | lineWriter.write(lines); 63 | logger().info(String.format("Rules were written to '%s'.", file)); 64 | }; 65 | } 66 | 67 | private static Path getFile(String path) { 68 | Path outputFile = Paths.get(path); 69 | if (Files.isDirectory(outputFile)) 70 | return outputFile.resolve(DEFAULT_FILE_NAME); 71 | else 72 | return outputFile; 73 | } 74 | 75 | private ResultOutputStrategy createFailingStrategy() { 76 | return new FailBuildResultOutputStrategy(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/PairCollector.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.codefx.mvn.jdeps.tool.PairCollector.Pair; 5 | 6 | import java.util.Set; 7 | import java.util.function.BiConsumer; 8 | import java.util.function.BinaryOperator; 9 | import java.util.function.Function; 10 | import java.util.function.Supplier; 11 | import java.util.stream.Collector; 12 | 13 | /** 14 | * Uses two collectors on a stream of pairs to create a pair of collection results. 15 | * 16 | * @param 17 | * the type of input elements to the reduction operation 18 | * @param 19 | * the result type of the first reduction operation 20 | * @param 21 | * the result type of the second reduction operation 22 | * @param 23 | * the mutable accumulation type of the first reduction operation 24 | * @param 25 | * the mutable accumulation type of the second reduction operation 26 | */ 27 | public class PairCollector implements Collector, Pair> { 28 | 29 | /* 30 | * TODO: This class should be polished, tested and placed somewhere else. 31 | */ 32 | 33 | private final Collector firstCollector; 34 | private final Collector secondCollector; 35 | 36 | private PairCollector(Collector firstCollector, Collector secondCollector) { 37 | this.firstCollector = firstCollector; 38 | this.secondCollector = secondCollector; 39 | } 40 | 41 | public static Collector> pairing( 42 | Collector firstCollector, Collector secondCollector) { 43 | return new PairCollector<>(firstCollector, secondCollector); 44 | } 45 | 46 | @Override 47 | public Supplier> supplier() { 48 | return () -> new Pair<>(firstCollector.supplier().get(), secondCollector.supplier().get()); 49 | } 50 | 51 | @Override 52 | public BiConsumer, T> accumulator() { 53 | return (containers, newValue) -> { 54 | firstCollector.accumulator().accept(containers.first, newValue); 55 | secondCollector.accumulator().accept(containers.second, newValue); 56 | }; 57 | } 58 | 59 | @Override 60 | public BinaryOperator> combiner() { 61 | return (containers, otherContainers) -> { 62 | CA firstNewContainer = firstCollector.combiner().apply(containers.first, otherContainers.first); 63 | CB secondNewContainer = secondCollector.combiner().apply(containers.second, otherContainers.second); 64 | return new Pair<>(firstNewContainer, secondNewContainer); 65 | }; 66 | } 67 | 68 | @Override 69 | public Function, Pair> finisher() { 70 | return containers -> { 71 | A firstResult = firstCollector.finisher().apply(containers.first); 72 | B secondResult = secondCollector.finisher().apply(containers.second); 73 | return new Pair<>(firstResult, secondResult); 74 | }; 75 | } 76 | 77 | @Override 78 | public Set characteristics() { 79 | return Sets.intersection(firstCollector.characteristics(), secondCollector.characteristics()); 80 | } 81 | 82 | public static class Pair { 83 | 84 | public final A first; 85 | public final B second; 86 | 87 | Pair(A first, B second) { 88 | this.first = first; 89 | this.second = second; 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/result/ViolationsToRuleTransformerTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.apache.maven.plugin.MojoFailureException; 4 | import org.codefx.mvn.jdeps.rules.DependencyRule; 5 | import org.codefx.mvn.jdeps.rules.Severity; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import java.util.List; 10 | import java.util.stream.Stream; 11 | 12 | import static java.util.stream.Collectors.toList; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.codefx.mvn.jdeps.Factory.violation; 15 | import static org.codefx.mvn.jdeps.result.ViolationsToRuleTransformer.transform; 16 | import static org.mockito.Matchers.any; 17 | import static org.mockito.Mockito.mock; 18 | import static org.mockito.Mockito.when; 19 | 20 | /** 21 | * Tests {@link ViolationsToRuleTransformer}. 22 | */ 23 | public class ViolationsToRuleTransformerTest { 24 | 25 | private Result result; 26 | 27 | @Before 28 | public void setUp() { 29 | result = mock(Result.class); 30 | } 31 | 32 | @Test(expected = NullPointerException.class) 33 | public void transform_resultNull_throwsException() throws MojoFailureException { 34 | transform(null); 35 | } 36 | 37 | @Test 38 | public void transform_resultEmpty_outputEmpty() throws Exception { 39 | when(result.violationsWithSeverity(any())) 40 | .then(ignored -> Stream.empty()); 41 | 42 | List rules = transform(result).collect(toList()); 43 | 44 | assertThat(rules).isEmpty(); 45 | } 46 | 47 | @Test 48 | public void transform_oneViolationPerSeverity_outputAllViolations() throws Exception { 49 | when(result.violationsWithSeverity(Severity.IGNORE)) 50 | .thenReturn(Stream.of(violation("org.Ignore", "sun.Ignore"))); 51 | when(result.violationsWithSeverity(Severity.SUMMARIZE)) 52 | .thenReturn(Stream.of(violation("org.Summarize", "sun.Summarize"))); 53 | when(result.violationsWithSeverity(Severity.INFORM)) 54 | .thenReturn(Stream.of(violation("org.Inform", "sun.Inform"))); 55 | when(result.violationsWithSeverity(Severity.WARN)) 56 | .thenReturn(Stream.of(violation("org.Warn", "sun.Warn"))); 57 | when(result.violationsWithSeverity(Severity.FAIL)) 58 | .thenReturn(Stream.of(violation("org.Fail", "sun.Fail"))); 59 | 60 | List rules = transform(result).collect(toList()); 61 | 62 | assertThat(rules).containsOnly( 63 | DependencyRule.of("org.Ignore", "sun.Ignore", Severity.IGNORE), 64 | DependencyRule.of("org.Summarize", "sun.Summarize", Severity.SUMMARIZE), 65 | DependencyRule.of("org.Inform", "sun.Inform", Severity.INFORM), 66 | DependencyRule.of("org.Warn", "sun.Warn", Severity.WARN), 67 | DependencyRule.of("org.Fail", "sun.Fail", Severity.FAIL) 68 | ); 69 | } 70 | 71 | @Test 72 | public void transform_oneViolationWithManyDependencies_outputContainsOneRulePerDependency() throws Exception { 73 | when(result.violationsWithSeverity(any())) 74 | .then(ignored -> Stream.empty()); 75 | when(result.violationsWithSeverity(Severity.IGNORE)) 76 | .thenReturn(Stream.of(violation("org.A", "sun.X", "sun.Y", "sun.Z"))); 77 | 78 | List rules = transform(result).collect(toList()); 79 | 80 | assertThat(rules).containsOnly( 81 | DependencyRule.of("org.A", "sun.X", Severity.IGNORE), 82 | DependencyRule.of("org.A", "sun.Y", Severity.IGNORE), 83 | DependencyRule.of("org.A", "sun.Z", Severity.IGNORE) 84 | ); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/XmlRule.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 4 | 5 | import java.util.stream.Stream; 6 | 7 | import static java.lang.String.format; 8 | import static java.util.Objects.requireNonNull; 9 | 10 | /** 11 | * A dependency rule {@code (Dependent -> Dependency: Severity)} defined in full XML. 12 | *

13 | * This class is used as a vehicle for the Mojo's configuration parameters. Note that it must be {@code public}, 14 | * requires a parameterless constructor and that changing its name or the name of its fields would break existing 15 | * configurations. 16 | */ 17 | public class XmlRule { 18 | 19 | private String dependent; 20 | private String dependency; 21 | private Severity severity; 22 | 23 | /** 24 | * Default constructor for use by Maven's parameter injection. 25 | */ 26 | public XmlRule() { 27 | // no code required 28 | } 29 | 30 | /** 31 | * Creates a new XML rule {@code dependent -> dependency: severity}. 32 | */ 33 | public XmlRule(String dependent, String dependency, Severity severity) { 34 | this.dependent = requireNonNull(dependent, "The argument 'dependent' must not be null."); 35 | this.dependency = requireNonNull(dependency, "The argument 'dependency' must not be null."); 36 | this.severity = requireNonNull(severity, "The argument 'severity' must not be null."); 37 | } 38 | 39 | /** 40 | * Creates a new XML rule from the specified dependency rule. 41 | * 42 | * @param dependencyRule 43 | * the rule to create an XML rule from 44 | */ 45 | public XmlRule(DependencyRule dependencyRule) { 46 | dependent = dependencyRule.getDependent(); 47 | dependency = dependencyRule.getDependency(); 48 | severity = dependencyRule.getSeverity(); 49 | } 50 | 51 | /** 52 | * @return this rule as a {@link DependencyRule} 53 | * 54 | * @throws ConfigurationException 55 | * if creating the {@code DependencyRule} fails 56 | */ 57 | public DependencyRule asDependencyRule() throws ConfigurationException { 58 | try { 59 | return DependencyRule.of(dependent, dependency, severity); 60 | } catch (IllegalArgumentException ex) { 61 | throw new ConfigurationException(ex.getMessage()); 62 | } 63 | } 64 | 65 | public String getDependent() { 66 | return dependent; 67 | } 68 | 69 | public String getDependency() { 70 | return dependency; 71 | } 72 | 73 | public Severity getSeverity() { 74 | return severity; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return format("(%s -> %s: %s)", dependent, dependency, severity); 80 | } 81 | 82 | /** 83 | * Creates an XML string representing this rule. 84 | * 85 | * @param indent 86 | * the string used to indent inner XML, possible {@code "\t"} 87 | * 88 | * @return this rule as an XML string 89 | */ 90 | public Stream toXmlLines(String indent) { 91 | requireNonNull(indent, "The argument 'indent' must not be null."); 92 | if (!indent.trim().isEmpty()) 93 | throw new IllegalArgumentException("The argument 'indent' must only consist of whitespace."); 94 | 95 | return Stream.of( 96 | "", 97 | indent + "" + dependent + "", 98 | indent + "" + dependency + "", 99 | indent + "" + severity + "", 100 | ""); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/DependenyRuleTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import static java.lang.String.format; 8 | 9 | /** 10 | * Tests {@link DependencyRule}. 11 | */ 12 | public class DependenyRuleTest { 13 | 14 | @Rule 15 | public ExpectedException thrown = ExpectedException.none(); 16 | 17 | @Test 18 | public void checkName_nameNull_throwsException() throws Exception { 19 | thrown.expect(IllegalArgumentException.class); 20 | thrown.expectMessage("The rule (null -> sun.misc.Unsafe: FAIL) defines no dependent."); 21 | 22 | letRuleCheckName(null); 23 | } 24 | 25 | @Test 26 | public void checkName_nameEmpty_throwsException() throws Exception { 27 | String faultyName = ""; 28 | 29 | thrown.expect(IllegalArgumentException.class); 30 | thrown.expectMessage(format("The rule (%s -> sun.misc.Unsafe: FAIL) defines no dependent.", faultyName)); 31 | 32 | letRuleCheckName(faultyName); 33 | } 34 | 35 | @Test 36 | public void checkName_firstPartEmpty_throwsException() throws Exception { 37 | String faultyName = ".foo.bar"; 38 | 39 | thrown.expect(IllegalArgumentException.class); 40 | thrown.expectMessage(format( 41 | "In the rule (%s -> sun.misc.Unsafe: FAIL) the name '%s' contains one empty part.", 42 | faultyName, faultyName)); 43 | 44 | letRuleCheckName(faultyName); 45 | } 46 | 47 | @Test 48 | public void checkName_middlePartEmpty_throwsException() throws Exception { 49 | String faultyName = "com..bar"; 50 | 51 | thrown.expect(IllegalArgumentException.class); 52 | thrown.expectMessage(format( 53 | "In the rule (%s -> sun.misc.Unsafe: FAIL) the name '%s' contains one empty part.", 54 | faultyName, faultyName)); 55 | 56 | letRuleCheckName(faultyName); 57 | } 58 | 59 | @Test 60 | public void checkName_lastPartEmpty_throwsException() throws Exception { 61 | String faultyName = "com.foo."; 62 | 63 | thrown.expect(IllegalArgumentException.class); 64 | thrown.expectMessage(format( 65 | "In the rule (%s -> sun.misc.Unsafe: FAIL) the name '%s' contains one empty part.", 66 | faultyName, faultyName)); 67 | 68 | letRuleCheckName(faultyName); 69 | } 70 | 71 | @Test 72 | public void checkName_illegalJavaIdentifierStart_throwsException() throws Exception { 73 | String faultyName = "1com.foo.bar"; 74 | 75 | thrown.expect(IllegalArgumentException.class); 76 | thrown.expectMessage(format( 77 | "In the rule (%s -> sun.misc.Unsafe: FAIL) " 78 | + "a part of the name '%s' starts with the invalid character '1'.", 79 | faultyName, faultyName)); 80 | 81 | letRuleCheckName(faultyName); 82 | } 83 | 84 | @Test 85 | public void checkName_illegalJavaIdentifierPart_throwsException() throws Exception { 86 | String faultyName = "com.fo-o.bar"; 87 | 88 | thrown.expect(IllegalArgumentException.class); 89 | thrown.expectMessage(format( 90 | "In the rule (%s -> sun.misc.Unsafe: FAIL) the name '%s' contains the invalid character '-'.", 91 | faultyName, faultyName)); 92 | 93 | letRuleCheckName(faultyName); 94 | } 95 | 96 | @Test 97 | public void checkName_nameIsWildcard_throwsNoException() throws Exception { 98 | letRuleCheckName(DependencyRule.ALL_TYPES_WILDCARD); 99 | } 100 | 101 | private static void letRuleCheckName(String faultyName) throws IllegalArgumentException { 102 | DependencyRule.checkName(faultyName, format("(%s -> sun.misc.Unsafe: FAIL)", faultyName), "dependent"); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDeps Mvn 2 | 3 | ###### The Only Maven Plugin That Understands JDeps 4 | 5 | --- 6 | 7 | This plugin is all about [JDeps](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jdeps.html) and particularly about [JDK-internal APIs](https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool). 8 | With Java 9 ([scheduled for 03/2017](http://mail.openjdk.java.net/pipermail/jdk9-dev/2015-December/003149.html)) such APIs [will become unavailable](http://blog.codefx.org/java/dev/how-java-9-and-project-jigsaw-may-break-your-code/), which may break a project. 9 | Redemption comes in four easy steps: 10 | 11 | 1. identify your projects' problematic dependencies 12 | 2. create a plan to move away from them 13 | 3. prevent relapses 14 | 4. do the same for your dependencies 15 | 16 | This plugin helps with :one: and :three:, :two: shouldn't be too hard in most cases but :four: might be. 17 | 18 | ## How Does It Help? :sparkles: 19 | 20 | Running `jdeps -jdkinternals` against the compiled classes it will **discover dependencies** on internal APIs. 21 | But it not only runs JDeps, it also understands its output, which allows a **convenient and detailed configuration** of when exactly the **build should break**. 22 | This enables a **self-paced migration** away from problematic dependencies with immediate **failure on relapses**. 23 | 24 | Since _JDeps Mvn_ actually **understands JDeps**, it is straight forward to define flexible rules for how dependencies are treated. So you have this package where the use of `sun.misc` is generally ok except for the one subpackage where everything but `Unsafe` could already be removed and must not creep back? No problem: 25 | 26 | ```xml 27 | org.food -> sun.misc: WARN 28 | org.food.fruit -> sun.misc: FAIL 29 | org.food.fruit -> sun.misc.Unsafe: WARN 30 | ``` 31 | 32 | You find it too tedious to write all these rules by hand? Also no problem. Check out the [walkthrough](https://github.com/CodeFX-org/JDeps-Maven-Plugin/wiki/Walkthrough) for more on rules, including how to create them automatically. 33 | 34 | ## Quick Start :rocket: 35 | 36 | To get a first impression how you're doing JDK-internal-wise simply run _JDeps Mvn_: 37 | 38 | ```bash 39 | mvn clean compile org.codefx.mvn:jdeps-maven-plugin:jdkinternals 40 | ``` 41 | 42 | This will log every dependency _jdeps_ reports to the console (on level `WARN`). 43 | 44 | Adding the following to your pom yields the same result as above but on every run: 45 | 46 | ```xml 47 | 48 | org.codefx.mvn 49 | jdeps-maven-plugin 50 | 0.2 51 | 52 | 53 | 54 | 55 | 56 | 57 | jdkinternals 58 | 59 | 60 | 61 | 62 | ``` 63 | 64 | To explicitly run this plugin you can do one of these: 65 | 66 | ```bash 67 | mvn clean compile jdeps:jdkinternals # just this plugin 68 | mvn verify # everything up to the phase in which this plugin runs 69 | ``` 70 | 71 | ## Next steps :anger: 72 | 73 | But we're all too good in ignoring log messages so check out the [walkthrough](https://github.com/CodeFX-org/JDeps-Maven-Plugin/wiki/Walkthrough) and the rest of the [wiki](https://github.com/CodeFX-org/JDeps-Maven-Plugin/wiki) for information on how to advance to the next level. 74 | 75 | ## Contact :eyes: 76 | 77 | Nicolai Parlog
78 | CodeFX 79 | 80 | Web: http://codefx.org
81 | Twitter: https://twitter.com/nipafx
82 | Mail: nipa@codefx.org
83 | PGP-Key: http://keys.gnupg.net/pks/lookup?op=vindex&search=0xA47A795BA5BF8326
84 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/TypeNameHierarchyTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Tests for {@link TypeNameHierarchy}. 9 | */ 10 | public class TypeNameHierarchyTest { 11 | 12 | @Test(expected = NullPointerException.class) 13 | public void forFullyQualifiedName_nameNull_throwsException() { 14 | TypeNameHierarchy.forFullyQualifiedName(null, PackageInclusion.HIERARCHICAL); 15 | } 16 | 17 | @Test(expected = NullPointerException.class) 18 | public void forFullyQualifiedName_packageInclusionNull_throwsException() { 19 | TypeNameHierarchy.forFullyQualifiedName("some.Class", null); 20 | } 21 | 22 | // #begin HIERARCHICAL PACKAGE INCLUSION 23 | 24 | @Test 25 | public void iterateHierarchical_oneLevelType_correctNames() { 26 | TypeNameHierarchy hierarchy = TypeNameHierarchy 27 | .forFullyQualifiedName("some.Class", PackageInclusion.HIERARCHICAL); 28 | assertThat(hierarchy).containsExactly("some.Class", "some"); 29 | } 30 | 31 | @Test 32 | public void iterateHierarchical_twoLevelType_correctNames() { 33 | TypeNameHierarchy hierarchy = TypeNameHierarchy 34 | .forFullyQualifiedName("some.deep.Class", PackageInclusion.HIERARCHICAL); 35 | assertThat(hierarchy).containsExactly("some.deep.Class", "some.deep", "some"); 36 | } 37 | 38 | @Test 39 | public void iterateHierarchical_threeLevelType_correctNames() { 40 | TypeNameHierarchy hierarchy = TypeNameHierarchy 41 | .forFullyQualifiedName("some.very.deep.Class", PackageInclusion.HIERARCHICAL); 42 | assertThat(hierarchy).containsExactly("some.very.deep.Class", "some.very.deep", "some.very", "some"); 43 | } 44 | 45 | @Test 46 | public void iterateHierarchical_threeLevelInnerType_correctNames() { 47 | TypeNameHierarchy hierarchy = TypeNameHierarchy 48 | .forFullyQualifiedName("some.very.deep.Inner.Class", PackageInclusion.HIERARCHICAL); 49 | assertThat(hierarchy).containsExactly( 50 | "some.very.deep.Inner.Class", "some.very.deep.Inner", "some.very.deep", "some.very", "some"); 51 | } 52 | 53 | // #begin HIERARCHICAL PACKAGE INCLUSION 54 | 55 | // #begin FLAT PACKAGE INCLUSION 56 | 57 | // edge cases 58 | 59 | @Test 60 | public void iterateFlat_noPackagePrefix_correctNames() { 61 | TypeNameHierarchy hierarchy = TypeNameHierarchy 62 | .forFullyQualifiedName("TopLevel.Class", PackageInclusion.FLAT); 63 | assertThat(hierarchy).containsExactly("TopLevel.Class", "TopLevel"); 64 | } 65 | 66 | // regular cases 67 | 68 | @Test 69 | public void iterateFlat_oneLevelType_correctNames() { 70 | TypeNameHierarchy hierarchy = TypeNameHierarchy 71 | .forFullyQualifiedName("some.Class", PackageInclusion.FLAT); 72 | assertThat(hierarchy).containsExactly("some.Class", "some"); 73 | } 74 | 75 | @Test 76 | public void iterateFlat_twoLevelType_correctNames() { 77 | TypeNameHierarchy hierarchy = TypeNameHierarchy 78 | .forFullyQualifiedName("some.deep.Class", PackageInclusion.FLAT); 79 | assertThat(hierarchy).containsExactly("some.deep.Class", "some.deep"); 80 | } 81 | 82 | @Test 83 | public void iterateFlat_threeLevelType_correctNames() { 84 | TypeNameHierarchy hierarchy = TypeNameHierarchy 85 | .forFullyQualifiedName("some.very.deep.Class", PackageInclusion.FLAT); 86 | assertThat(hierarchy).containsExactly("some.very.deep.Class", "some.very.deep"); 87 | } 88 | 89 | @Test 90 | public void iterateFlat_threeLevelInnerType_correctNames() { 91 | TypeNameHierarchy hierarchy = TypeNameHierarchy 92 | .forFullyQualifiedName("some.very.deep.Inner.Class", PackageInclusion.FLAT); 93 | assertThat(hierarchy).containsExactly( 94 | "some.very.deep.Inner.Class", "some.very.deep.Inner", "some.very.deep"); 95 | } 96 | 97 | // #end FLAT PACKAGE INCLUSION 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/dependency/TypeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.dependency; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Tests for {@link Type}. 9 | */ 10 | public class TypeTest { 11 | 12 | @Test(expected = NullPointerException.class) 13 | public void of_packageNameNull_throwsException() { 14 | Type.of(null, "Class"); 15 | } 16 | 17 | @Test(expected = IllegalArgumentException.class) 18 | public void of_packageNameEmpty_throwsException() { 19 | Type.of("", "Class"); 20 | } 21 | 22 | @Test(expected = NullPointerException.class) 23 | public void of_classNameNull_throwsException() { 24 | Type.of("java.lang", null); 25 | } 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void of_classNameEmpty_throwsException() { 29 | Type.of("java.lang", ""); 30 | } 31 | 32 | @Test(expected = NullPointerException.class) 33 | public void of_fullNameNull_throwsException() { 34 | Type.of(null); 35 | } 36 | 37 | @Test(expected = IllegalArgumentException.class) 38 | public void of_fullNameEmpty_throwsException() { 39 | Type.of(""); 40 | } 41 | 42 | @Test(expected = IllegalArgumentException.class) 43 | public void of_fullNameWithoutDot_throwsException() { 44 | Type.of("ClassName"); 45 | } 46 | 47 | @Test 48 | public void getPackageName_createdFromPackageAndClassName_returnsCorrectName() { 49 | Type type = Type.of("java.lang", "Class"); 50 | assertThat(type.getPackageName()).isEqualTo("java.lang"); 51 | } 52 | 53 | @Test 54 | public void getClassName_createdFromPackageAndClassName_returnsCorrectName() { 55 | Type type = Type.of("java.lang", "Class"); 56 | assertThat(type.getClassName()).isEqualTo("Class"); 57 | } 58 | 59 | @Test 60 | public void getFullName_createdFromPackageAndClassName_returnsCorrectName() { 61 | Type type = Type.of("java.lang", "Class"); 62 | assertThat(type.getFullyQualifiedName()).isEqualTo("java.lang.Class"); 63 | } 64 | 65 | @Test 66 | public void getPackageName_createdFromFullName_returnsCorrectName() { 67 | Type type = Type.of("java.lang.Class"); 68 | assertThat(type.getPackageName()).isEqualTo("java.lang"); 69 | } 70 | 71 | @Test 72 | public void getClassName_createdFromFullName_returnsCorrectName() { 73 | Type type = Type.of("java.lang.Class"); 74 | assertThat(type.getClassName()).isEqualTo("Class"); 75 | } 76 | 77 | @Test 78 | public void getFullName_createdFromFullName_returnsCorrectName() { 79 | Type type = Type.of("java.lang.Class"); 80 | assertThat(type.getFullyQualifiedName()).isEqualTo("java.lang.Class"); 81 | } 82 | 83 | @Test 84 | public void compareTo_differentPackageNames_orderedByPackageName() throws Exception { 85 | // note how if the types were sorted by their simple name, 'Optional' would be smaller than 'String' 86 | Type smaller = Type.of("java.lang", "String"); 87 | Type greater = Type.of("java.util", "Optional"); 88 | 89 | assertThat(smaller.compareTo(greater)).isNegative(); 90 | assertThat(greater.compareTo(smaller)).isPositive(); 91 | } 92 | 93 | @Test 94 | public void compareTo_samePackageButDifferentClassNames_orderedByClassName() throws Exception { 95 | Type smaller = Type.of("java.lang", "Object"); 96 | Type greater = Type.of("java.lang", "String"); 97 | 98 | assertThat(smaller.compareTo(greater)).isNegative(); 99 | assertThat(greater.compareTo(smaller)).isPositive(); 100 | } 101 | 102 | @Test 103 | public void compareTo_sameFullyQualifiedName_orderedSame() throws Exception { 104 | Type one = Type.of("java.lang", "Object"); 105 | Type other = Type.of("java.lang", "Object"); 106 | 107 | assertThat(one.compareTo(other)).isZero(); 108 | assertThat(other.compareTo(one)).isZero(); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/AnnotatedViolation.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import org.codefx.mvn.jdeps.dependency.InternalType; 6 | import org.codefx.mvn.jdeps.dependency.Type; 7 | import org.codefx.mvn.jdeps.dependency.Violation; 8 | import org.codefx.mvn.jdeps.rules.Severity; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import java.util.function.Predicate; 15 | 16 | import static java.util.Objects.requireNonNull; 17 | import static java.util.stream.Collectors.collectingAndThen; 18 | import static java.util.stream.Collectors.groupingBy; 19 | import static java.util.stream.Collectors.mapping; 20 | import static java.util.stream.Collectors.toList; 21 | 22 | /** 23 | * A violation whose dependencies are annotated with their severity. 24 | */ 25 | final class AnnotatedViolation { 26 | 27 | private final Type dependent; 28 | private ImmutableMap> internalDependencies; 29 | 30 | private AnnotatedViolation( 31 | Type dependent, 32 | ImmutableMap> internalDependencies) { 33 | this.dependent = dependent; 34 | this.internalDependencies = internalDependencies; 35 | } 36 | 37 | public static AnnotatedViolation of(Type dependent, ImmutableList internalDependencies) { 38 | requireNonNull(dependent, "The argument 'dependent' must not be null."); 39 | requireNonNull(internalDependencies, "The argument 'internalDependencies' must not be null."); 40 | if (internalDependencies.size() == 0) 41 | throw new IllegalArgumentException( 42 | "A violation must contain at least one internal dependency."); 43 | 44 | ImmutableMap> internalDependenciesMap = 45 | internalDependencies.stream().collect( 46 | collectingAndThen( 47 | groupingBy(AnnotatedInternalType::getSeverity, 48 | collectingAndThen(mapping( 49 | AnnotatedInternalType::getType, 50 | toList()), 51 | ImmutableList::copyOf)), 52 | ImmutableMap::copyOf)); 53 | return new AnnotatedViolation(dependent, internalDependenciesMap); 54 | } 55 | 56 | /** 57 | * Returns a violation that contains only the internal dependencies with the specified severities. 58 | *

59 | * If no internal dependencies for the specified severities exist, {@link Optional#empty()} is returned. 60 | * 61 | * @param severities 62 | * the severities to filterBy internal dependencies by 63 | * 64 | * @return a violation or {@link Optional#empty() empty} if no internal dependencies with the specified severity 65 | * exist 66 | */ 67 | public Optional only(Severity... severities) { 68 | List severitiesToSelect = Arrays.asList(severities); 69 | return filterBy(severitiesToSelect::contains); 70 | } 71 | 72 | /** 73 | * Returns a violation that contains all internal dependencies except the ones with one of the specified 74 | * severities. 75 | * 76 | * @return a violation or {@link Optional#empty() empty} if no internal dependencies with the non-excluded 77 | * severities exist 78 | */ 79 | public Optional except(Severity... severities) { 80 | List severitiesToIgnore = Arrays.asList(severities); 81 | return filterBy(severity -> !severitiesToIgnore.contains(severity)); 82 | } 83 | 84 | private Optional filterBy(Predicate filterBySeverity) { 85 | List dependencies = Severity.stream() 86 | .filter(filterBySeverity) 87 | .map(internalDependencies::get) 88 | .filter(Objects::nonNull) 89 | .flatMap(ImmutableList::stream) 90 | .collect(toList()); 91 | 92 | if (dependencies.isEmpty()) 93 | return Optional.empty(); 94 | else 95 | return Optional.of(Violation.buildFor(dependent, dependencies)); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/parse/InternalTypeLineParserTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.parse; 2 | 3 | import org.codefx.mvn.jdeps.dependency.InternalType; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.Optional; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.junit.Assert.assertFalse; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | /** 14 | * Tests the class {@link InternalTypeLineParser}. 15 | */ 16 | public class InternalTypeLineParserTest { 17 | 18 | private InternalTypeLineParser parser; 19 | 20 | @Before 21 | public void setup() { 22 | parser = new InternalTypeLineParser(); 23 | } 24 | 25 | // isInternalTypeLine 26 | 27 | @Test(expected = NullPointerException.class) 28 | public void isInternalTypeLine_nullLine_throwsException() throws Exception { 29 | parser.isInternalTypeLine(null); 30 | } 31 | 32 | @Test 33 | public void isInternalTypeLine_emptyLine_returnsFalse() throws Exception { 34 | boolean isInternalTypeLine = parser.isInternalTypeLine(""); 35 | 36 | assertFalse(isInternalTypeLine); 37 | } 38 | 39 | @Test 40 | public void isInternalTypeLine_matchingLine$1_returnsTrue() throws Exception { 41 | String example = " -> sun.misc.BASE64Decoder JDK internal API (rt.jar)"; 42 | boolean isInternalTypeLine = parser.isInternalTypeLine(example); 43 | 44 | assertTrue(isInternalTypeLine); 45 | } 46 | 47 | @Test 48 | public void isInternalTypeLine_matchingLine$2_returnsTrue() throws Exception { 49 | String example = " -> sun.misc.Unsafe JDK internal API (rt.jar)"; 50 | boolean isInternalTypeLine = parser.isInternalTypeLine(example); 51 | 52 | assertTrue(isInternalTypeLine); 53 | } 54 | 55 | @Test 56 | public void isInternalTypeLine_matchingLineStartingWithCapitalLetters_returnsTrue() throws Exception { 57 | String example = " -> Sun.misc.Unsafe JDK internal API (rt.jar)"; 58 | boolean isInternalTypeLine = parser.isInternalTypeLine(example); 59 | 60 | assertTrue(isInternalTypeLine); 61 | } 62 | 63 | // parseLine 64 | 65 | @Test(expected = NullPointerException.class) 66 | public void parseLine_nullLine_throwsException() throws Exception { 67 | parser.parseLine(null); 68 | } 69 | 70 | @Test 71 | public void parseLine_emptyLine_returnsFalse() throws Exception { 72 | Optional type = parser.parseLine(""); 73 | 74 | assertFalse(type.isPresent()); 75 | } 76 | 77 | @Test 78 | public void parseLine_matchingLine$1_returnsTrue() throws Exception { 79 | String example = " -> sun.misc.BASE64Decoder JDK internal API (rt.jar)"; 80 | Optional type = parser.parseLine(example); 81 | 82 | assertThat(type) 83 | .isPresent() 84 | .contains(InternalType.of("sun.misc", "BASE64Decoder", "JDK internal API", "rt.jar")); 85 | } 86 | 87 | @Test 88 | public void parseLine_matchingLine$2_returnsTrue() throws Exception { 89 | String example = " -> sun.misc.Unsafe JDK internal API (rt.jar)"; 90 | Optional type = parser.parseLine(example); 91 | 92 | assertThat(type) 93 | .isPresent() 94 | .contains(InternalType.of("sun.misc", "Unsafe", "JDK internal API", "rt.jar")); 95 | } 96 | 97 | @Test 98 | public void parseLine_matchingLineStartingWithCapitalLetters_returnsTrue() throws Exception { 99 | String example = " -> Sun.misc.Unsafe JDK internal API (rt.jar)"; 100 | Optional type = parser.parseLine(example); 101 | 102 | assertThat(type) 103 | .isPresent() 104 | .contains(InternalType.of("Sun.misc", "Unsafe", "JDK internal API", "rt.jar")); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/result/LogResultOutputStrategy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import org.codefx.mvn.jdeps.dependency.Violation; 4 | import org.codefx.mvn.jdeps.tool.PairCollector.Pair; 5 | 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Consumer; 8 | import java.util.stream.Stream; 9 | 10 | import static java.lang.String.format; 11 | import static java.util.stream.Collectors.reducing; 12 | import static java.util.stream.Collectors.summingInt; 13 | import static org.codefx.mvn.jdeps.mojo.MojoLogging.logger; 14 | import static org.codefx.mvn.jdeps.tool.PairCollector.pairing; 15 | 16 | /** 17 | * A {@link ResultOutputStrategy} that uses the Mojos facilities to log violations. 18 | */ 19 | public class LogResultOutputStrategy implements ResultOutputStrategy { 20 | 21 | static final String MESSAGE_ABOUT_JDEPS = 22 | "JDeps reported dependencies on JDK-internal APIs. "; 23 | private static final String MESSAGE_NO_DEPENDENCIES = 24 | "JDeps reported no dependencies on JDK-internal APIs."; 25 | 26 | private static final String MESSAGE_SUMMARIZE_DEPENDENCIES = 27 | MESSAGE_ABOUT_JDEPS + "Configured for SUMMARY are %1$s."; 28 | private static final String MESSAGE_INFORM_DEPENDENCIES = 29 | MESSAGE_ABOUT_JDEPS + "Configured to INFORM are %1$s:"; 30 | private static final String MESSAGE_WARN_DEPENDENCIES = 31 | MESSAGE_ABOUT_JDEPS + "Configured to WARN are %1$s:"; 32 | private static final String MESSAGE_FAIL_DEPENDENCIES = 33 | MESSAGE_ABOUT_JDEPS + "Configured to FAIL are %1$s:"; 34 | 35 | @Override 36 | public void output(Result result) { 37 | logger().debug("Printing analysis results..."); 38 | 39 | int violationsCount = logNumberOfViolationsToSummarize(result); 40 | violationsCount += logViolationsToInform(result); 41 | violationsCount += logViolationsToWarn(result); 42 | violationsCount += logViolationsToFail(result); 43 | 44 | if (violationsCount == 0) 45 | logZeroDependencies(message -> logger().info(message)); 46 | } 47 | 48 | private int logNumberOfViolationsToSummarize(Result result) { 49 | return countAndIfViolationsExist( 50 | result.violationsToSummarize(), 51 | (count, ignoredViolationDetails) -> logger().info(format(MESSAGE_SUMMARIZE_DEPENDENCIES, count))); 52 | } 53 | 54 | private int logViolationsToInform(Result result) { 55 | return logViolations( 56 | result.violationsToInform(), MESSAGE_INFORM_DEPENDENCIES, message -> logger().info(message)); 57 | } 58 | 59 | private int logViolationsToWarn(Result result) { 60 | return logViolations( 61 | result.violationsToWarn(), MESSAGE_WARN_DEPENDENCIES, message -> logger().warn(message)); 62 | } 63 | 64 | private int logViolationsToFail(Result result) { 65 | return logViolations( 66 | result.violationsToFail(), MESSAGE_FAIL_DEPENDENCIES, message -> logger().error(message)); 67 | } 68 | 69 | private int logViolations(Stream violations, String messageFormat, Consumer log) { 70 | return countAndIfViolationsExist( 71 | violations, 72 | (count, violationLines) -> logMessage(messageFormat, count, violationLines, log)); 73 | } 74 | 75 | private void logMessage( 76 | String messageFormat, int violationsCount, Stream violationLines, Consumer log) { 77 | log.accept(format(messageFormat, violationsCount)); 78 | violationLines.forEach(log); 79 | } 80 | 81 | private int countAndIfViolationsExist( 82 | Stream violations, BiConsumer> handleViolations) { 83 | Pair> countAndMessage = violations 84 | .collect(pairing( 85 | summingInt(violation -> violation.getInternalDependencies().size()), 86 | reducing(Stream.of(), Violation::toLines, Stream::concat))); 87 | if (countAndMessage.first == 0) 88 | return 0; 89 | 90 | handleViolations.accept(countAndMessage.first, countAndMessage.second); 91 | return countAndMessage.first; 92 | } 93 | 94 | private void logZeroDependencies(Consumer log) { 95 | log.accept(MESSAGE_NO_DEPENDENCIES); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/mojo/JdkInternalsMojo.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import org.apache.maven.plugin.AbstractMojo; 4 | import org.apache.maven.plugin.MojoExecutionException; 5 | import org.apache.maven.plugin.MojoFailureException; 6 | import org.apache.maven.plugins.annotations.Mojo; 7 | import org.apache.maven.plugins.annotations.Parameter; 8 | import org.codefx.mvn.jdeps.result.Result; 9 | import org.codefx.mvn.jdeps.result.ResultOutputStrategy; 10 | import org.codefx.mvn.jdeps.result.RuleOutputFormat; 11 | import org.codefx.mvn.jdeps.rules.PackageInclusion; 12 | import org.codefx.mvn.jdeps.rules.Severity; 13 | import org.codefx.mvn.jdeps.rules.XmlRule; 14 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 15 | import org.codehaus.plexus.util.cli.CommandLineException; 16 | 17 | import java.io.File; 18 | import java.nio.file.Paths; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | import static org.apache.maven.plugins.annotations.LifecyclePhase.VERIFY; 23 | import static org.apache.maven.plugins.annotations.ResolutionScope.COMPILE; 24 | import static org.codefx.mvn.jdeps.mojo.MojoLogging.logger; 25 | 26 | /** 27 | * Runs "jdeps -jdkinternals" and breaks the build if the tool reports dependencies on JDK internal API. 28 | */ 29 | @Mojo(name = "jdkinternals", 30 | threadSafe = true, 31 | requiresProject = true, 32 | defaultPhase = VERIFY, 33 | requiresDependencyResolution = COMPILE) 34 | public class JdkInternalsMojo extends AbstractMojo { 35 | 36 | @Parameter 37 | private Severity defaultSeverity = Severity.WARN; 38 | 39 | @Parameter 40 | private PackageInclusion packages = PackageInclusion.FLAT; 41 | 42 | @Parameter 43 | private List xmlDependencyRules = new ArrayList<>(); 44 | 45 | @Parameter 46 | private List arrowDependencyRules = new ArrayList<>(); 47 | 48 | @Parameter(defaultValue = "${project.build.outputDirectory}", readonly = true) 49 | private File buildOutputDirectory; 50 | 51 | @Parameter 52 | private boolean outputRulesForViolations = false; 53 | 54 | @Parameter 55 | private RuleOutputFormat outputRuleFormat = RuleOutputFormat.XML; 56 | 57 | @Parameter(defaultValue = "${project.build.outputDirectory}") 58 | private String outputFilePath = ""; 59 | 60 | @Override 61 | public void execute() throws MojoExecutionException, MojoFailureException { 62 | MojoLogging.registerLogger(this::getLog); 63 | logPluginStart(); 64 | executePlugin(); 65 | MojoLogging.unregisterLogger(); 66 | } 67 | 68 | private void logPluginStart() { 69 | logger().debug("Hello from JDeps-Maven-Plugin!"); 70 | logger().debug("Configuration:"); 71 | logger().debug("\tdefaultSeverity = " + defaultSeverity); 72 | logger().debug("\tpackages = " + packages); 73 | logger().debug("\toutputRulesForViolations = " + outputRulesForViolations); 74 | if (outputRulesForViolations) { 75 | logger().debug("\toutputRuleFormat = " + outputRuleFormat); 76 | logger().debug("\toutputFilePath = " + outputFilePath); 77 | } 78 | } 79 | 80 | private void executePlugin() throws MojoExecutionException, MojoFailureException { 81 | Result result = executeJDeps(); 82 | outputResult(result); 83 | } 84 | 85 | private void outputResult(Result result) throws MojoFailureException { 86 | ResultOutputStrategy outputStrategy = new OutputConfiguration( 87 | outputRulesForViolations, outputRuleFormat, outputFilePath) 88 | .createOutputStrategy(); 89 | outputStrategy.output(result); 90 | } 91 | 92 | private Result executeJDeps() throws MojoExecutionException { 93 | try { 94 | return JdkInternalsExecutionService.execute( 95 | Paths.get(buildOutputDirectory.toURI()), 96 | new DependencyRulesConfiguration( 97 | defaultSeverity, packages, xmlDependencyRules, arrowDependencyRules) 98 | ); 99 | } catch (CommandLineException ex) { 100 | throw new MojoExecutionException("Executing 'jdeps -jdkinternals' failed.", ex); 101 | } catch (ConfigurationException ex) { 102 | throw new MojoExecutionException("Parsing the configuration failed.", ex); 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/dependency/Type.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.dependency; 2 | 3 | import java.util.Comparator; 4 | import java.util.Objects; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * A simple textual representation of a type consisting of the package and the class name. 10 | */ 11 | public class Type implements Comparable { 12 | 13 | private static final Comparator COMPARATOR = Comparator 14 | .comparing(Type::getPackageName) 15 | .thenComparing(Type::getClassName); 16 | 17 | private final String packageName; 18 | private final String className; 19 | 20 | /** 21 | * Creates a new type. 22 | * 23 | * @param packageName 24 | * the name of the package containing the type (dotted) 25 | * @param className 26 | * the name of the type's class (dotted) 27 | */ 28 | protected Type(String packageName, String className) { 29 | this.packageName = requireNonNull(packageName, "The argument 'packageName' must not be null."); 30 | this.className = requireNonNull(className, "The argument 'className' must not be null."); 31 | 32 | if (packageName.isEmpty()) 33 | throw new IllegalArgumentException("The argument 'packageName' must not be empty."); 34 | if (className.isEmpty()) 35 | throw new IllegalArgumentException("The argument 'className' must not be empty."); 36 | } 37 | 38 | /** 39 | * Returns a type for the specified package and class name. 40 | * 41 | * @param packageName 42 | * the name of the package containing the type (dotted) 43 | * @param className 44 | * the name of the type's class (dotted) 45 | * 46 | * @return a type 47 | */ 48 | public static Type of(String packageName, String className) { 49 | return new Type(packageName, className); 50 | } 51 | 52 | /** 53 | * Returns a type for the specified fully qualified class name. 54 | * 55 | * @param qualifiedClassName 56 | * the fully qualified name of the type's class (dotted) 57 | * 58 | * @return a type 59 | */ 60 | public static Type of(String qualifiedClassName) { 61 | requireNonNull(qualifiedClassName, "The argument 'qualifiedClassName' must not be null."); 62 | if (qualifiedClassName.isEmpty()) 63 | throw new IllegalArgumentException("The argument 'qualifiedClassName' must not be empty."); 64 | 65 | int lastDotIndex = qualifiedClassName.lastIndexOf('.'); 66 | if (lastDotIndex == -1) 67 | throw new IllegalArgumentException( 68 | "The argument 'qualifiedClassName' must be a fully qualified class name " 69 | + "with at least one dot ('.')."); 70 | 71 | String packageName = qualifiedClassName.substring(0, lastDotIndex); 72 | String className = qualifiedClassName.substring(lastDotIndex + 1); 73 | return new Type(packageName, className); 74 | } 75 | 76 | /** 77 | * @return the dotted name of this type's package 78 | */ 79 | public String getPackageName() { 80 | return packageName; 81 | } 82 | 83 | /** 84 | * @return this type's class name 85 | */ 86 | public String getClassName() { 87 | return className; 88 | } 89 | 90 | /** 91 | * @return this type's fully qualified name, i.e. 92 | * {@link #getPackageName() packageName}.{@link #getClassName() className} 93 | */ 94 | public String getFullyQualifiedName() { 95 | return packageName + "." + className; 96 | } 97 | 98 | // #begin COMPARETO / EQUALS / HASHCODE / TOSTRING 99 | 100 | @Override 101 | public final int compareTo(Type other) { 102 | if (this == other) 103 | return 0; 104 | 105 | return COMPARATOR.compare(this, other); 106 | } 107 | 108 | @Override 109 | public final boolean equals(Object obj) { 110 | if (this == obj) 111 | return true; 112 | if (obj == null) 113 | return false; 114 | if (!(obj instanceof Type)) 115 | return false; 116 | 117 | Type other = (Type) obj; 118 | return Objects.equals(this.packageName, other.packageName) 119 | && Objects.equals(this.className, other.className); 120 | } 121 | 122 | @Override 123 | public final int hashCode() { 124 | return Objects.hash(packageName, className); 125 | } 126 | 127 | @Override 128 | public String toString() { 129 | return packageName + "." + className; 130 | } 131 | 132 | // #end COMPARETO / EQUALS / HASHCODE / TOSTRING 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/TypeNameHierarchy.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import java.util.Iterator; 6 | 7 | import static java.lang.Integer.max; 8 | import static java.lang.String.format; 9 | import static java.lang.String.join; 10 | import static java.util.Objects.requireNonNull; 11 | 12 | /** 13 | * Splits a type's or package's name into a an iteration of successivley more general names. 14 | *

15 | * Depending on the {@link PackageInclusion} specified during construction, this might include only the package 16 | * containing the type or all "super packages". 17 | *

18 | * E.g.: "java.lang.String" to { "java.lang.String", "java.lang", "java" } or to { "java.lang.String", "java.lang" }. 19 | */ 20 | final class TypeNameHierarchy implements Iterable { 21 | 22 | private final ImmutableList hierarchy; 23 | 24 | private TypeNameHierarchy(ImmutableList hierarchy) { 25 | this.hierarchy = 26 | requireNonNull(hierarchy, "The argument 'hierarchy' must not be null."); 27 | for (int i = 1; i < hierarchy.size(); i++) { 28 | String shorterPrefix = hierarchy.get(i) + "."; 29 | String longerPrefix = hierarchy.get(i - 1); 30 | if (!longerPrefix.startsWith(shorterPrefix)) 31 | throw new IllegalArgumentException(); 32 | } 33 | } 34 | 35 | /** 36 | * @param fullName 37 | * the fully qualified name of the type or package for which the hierarchy will be created 38 | * @param packageInclusion 39 | * determines which packages to include in the resulting hierarchy; for {@link PackageInclusion#FLAT FLAT} 40 | * this will only be the package containing the type; for 41 | * {@link PackageInclusion#HIERARCHICAL HIERARCHICAL} it will be all "super packages" 42 | * 43 | * @return a type name hierarchy for the specified name including the specified packages 44 | */ 45 | public static TypeNameHierarchy forFullyQualifiedName(String fullName, PackageInclusion packageInclusion) { 46 | requireNonNull(fullName, "The argument 'fullName' must not be null."); 47 | requireNonNull(packageInclusion, "The argument 'packageInclusion' must not be null."); 48 | 49 | String[] nameParts = fullName.split("\\."); 50 | int indexOfTopmostNamePart = indexOfTopmostNamePart(nameParts, packageInclusion); 51 | String[] hierarchy = new String[nameParts.length - indexOfTopmostNamePart]; 52 | 53 | String partialName = ""; 54 | for (int i = 0; i < nameParts.length; i++) { 55 | // append next name part 56 | if (i == 0) 57 | partialName = nameParts[0]; 58 | else 59 | partialName += "." + nameParts[i]; 60 | // add name to hierarchy if contains topmost package 61 | if (i >= indexOfTopmostNamePart) { 62 | // the index is counted from the back because we create the less specific names first 63 | // but want the more specific names to appear at the front of the array 64 | int indexInHierarchy = nameParts.length - 1 - i; 65 | hierarchy[indexInHierarchy] = partialName; 66 | } 67 | } 68 | 69 | return new TypeNameHierarchy(ImmutableList.copyOf(hierarchy)); 70 | } 71 | 72 | private static int indexOfTopmostNamePart(String[] nameParts, PackageInclusion packageInclusion) { 73 | switch (packageInclusion) { 74 | case FLAT: 75 | return indexOfContainingPackage(nameParts); 76 | case HIERARCHICAL: 77 | return 0; 78 | default: 79 | throw new IllegalArgumentException(format("Unknown inclusion '%s'.", packageInclusion)); 80 | } 81 | } 82 | 83 | private static int indexOfContainingPackage(String[] nameParts) { 84 | for (int i = 0; i < nameParts.length; i++) { 85 | char firstLetter = getFirstLetter(nameParts, i); 86 | boolean foundTypeName = Character.isUpperCase(firstLetter); 87 | if (foundTypeName) 88 | return max(i - 1, 0); 89 | } 90 | return 0; 91 | } 92 | 93 | private static char getFirstLetter(String[] nameParts, int index) { 94 | String namePart = nameParts[index]; 95 | if (namePart.isEmpty()) 96 | throw new IllegalArgumentException( 97 | format("The name %scontained an empty name.", join(".", nameParts))); 98 | return namePart.charAt(0); 99 | } 100 | 101 | @Override 102 | public Iterator iterator() { 103 | return hierarchy.iterator(); 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/jdeps/JdkInternalsExecutor.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool.jdeps; 2 | 3 | import org.codefx.mvn.jdeps.mojo.MojoLogging; 4 | import org.codefx.mvn.jdeps.parse.ViolationParser; 5 | import org.codehaus.plexus.util.cli.CommandLineException; 6 | import org.codehaus.plexus.util.cli.CommandLineUtils; 7 | import org.codehaus.plexus.util.cli.CommandLineUtils.StringStreamConsumer; 8 | import org.codehaus.plexus.util.cli.Commandline; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.StringReader; 12 | import java.nio.file.Path; 13 | import java.util.Objects; 14 | import java.util.function.Consumer; 15 | import java.util.stream.Stream; 16 | 17 | import static java.lang.String.format; 18 | 19 | /** 20 | * Executes "jdeps -jdkinternals". 21 | */ 22 | public class JdkInternalsExecutor { 23 | 24 | private final Path jDepsExecutable; 25 | private final Path pathToCheckedFiles; 26 | private final Consumer jDepsOutputConsumer; 27 | 28 | /** 29 | * Creates a new executor. 30 | * 31 | * @param jDepsExecutable 32 | * path to the JDeps executable 33 | * @param folderToScan 34 | * the path to the folder which jdeps will scan 35 | * @param jDepsOutputConsumer 36 | * consumer of jdeps' output (line by line) 37 | */ 38 | public JdkInternalsExecutor(Path jDepsExecutable, Path folderToScan, Consumer jDepsOutputConsumer) { 39 | Objects.requireNonNull(jDepsExecutable, "The argument 'jDepsExecutable' must not be null."); 40 | Objects.requireNonNull(folderToScan, "The argument 'pathToCheckedFiles' must not be null."); 41 | Objects.requireNonNull(jDepsOutputConsumer, "The argument 'jDepsOutputConsumer' must not be null."); 42 | 43 | this.jDepsExecutable = jDepsExecutable; 44 | this.pathToCheckedFiles = folderToScan; 45 | this.jDepsOutputConsumer = jDepsOutputConsumer; 46 | } 47 | 48 | // #begin EXECUTE JDEPS 49 | 50 | /** 51 | * Executes jdeps. 52 | * 53 | * @throws CommandLineException 54 | * if running jdeps failed or the tool returned with an error 55 | */ 56 | public void execute() throws CommandLineException { 57 | Commandline jDepsCommand = createJDepsCommand(jDepsExecutable); 58 | execute(jDepsCommand); 59 | } 60 | 61 | private Commandline createJDepsCommand(Path jDepsExecutable) { 62 | Commandline jDepsCommand = new Commandline(); 63 | jDepsCommand.setExecutable(jDepsExecutable.toAbsolutePath().toString()); 64 | jDepsCommand.createArg().setValue("-jdkinternals"); 65 | jDepsCommand.createArg().setFile(pathToCheckedFiles.toFile()); 66 | return jDepsCommand; 67 | } 68 | 69 | private void execute(Commandline jDepsCommand) throws CommandLineException { 70 | StringStreamConsumer errorConsoleConsumer = new StringStreamConsumer(); 71 | 72 | MojoLogging.logger().debug(format("Running JDeps: %s", jDepsCommand)); 73 | MojoLogging.logger().debug(String.format( 74 | "(JDeps output is forwarded here. " 75 | + "Lines are marked: %s = recognized as dependency; %s = not recognized.)", 76 | ViolationParser.MESSAGE_MARKER_JDEPS_LINE, 77 | ViolationParser.MESSAGE_MARKER_UNKNOWN_LINE)); 78 | 79 | int exitCode = CommandLineUtils.executeCommandLine( 80 | jDepsCommand, jDepsOutputConsumer::accept, errorConsoleConsumer); 81 | 82 | MojoLogging.logger().debug(format("JDeps completed with exit code %d.", exitCode)); 83 | 84 | if (exitCode != 0) 85 | throwCommandLineException(jDepsCommand, exitCode, errorConsoleConsumer.getOutput()); 86 | } 87 | 88 | private static void throwCommandLineException(Commandline jDepsCommand, int exitCode, String errorOutput) 89 | throws CommandLineException { 90 | StringBuilder message = new StringBuilder("JDeps returned with exit code '" + exitCode + "'.\n"); 91 | message.append("\t Executed command: " 92 | + CommandLineUtils.toString(jDepsCommand.getCommandline()).replaceAll("'", "")); 93 | message.append("\t Error output:\n"); 94 | streamLines(errorOutput).forEachOrdered(errorLine -> message.append("\t\t " + errorLine + "\n")); 95 | 96 | throw new CommandLineException(message.toString()); 97 | } 98 | 99 | private static Stream streamLines(String lines) { 100 | return new BufferedReader(new StringReader(lines)).lines(); 101 | } 102 | 103 | // #end EXECUTE JDEPS 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/ArrowRuleParser.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 5 | 6 | import java.util.Optional; 7 | import java.util.function.IntFunction; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import static java.lang.String.format; 12 | import static java.util.Objects.requireNonNull; 13 | 14 | /** 15 | * Parses rules of the form 16 | * {@code com.foo.Bar -> sun.misc.Unsafe: WARN} and {@code com.fo.Bar on sun.misc.Unsafe: WARN}. 17 | */ 18 | public class ArrowRuleParser { 19 | 20 | private static final String ERROR_MESSAGE_LINE_INVALID_RULE = "The line '%s' defines no valid rule."; 21 | private static final String ERROR_MESSAGE_MULTIPLE_RULES = 22 | "The line '%s' defines multiple rules. Please separate rules by a newline."; 23 | 24 | /** 25 | * Pattern to separate a string spanning multiple lines. 26 | */ 27 | private static final Pattern LINE_PATTERN = Pattern.compile("^", Pattern.MULTILINE); 28 | 29 | /** 30 | * Pattern to match a rule of the form {@code dependent -> dependency: severity}. 31 | */ 32 | private static final Pattern ARROW_RULE_PATTERN = Pattern.compile( 33 | "\\s*(\\S*)\\s*" + Arrow.REGULAR_EXPRESSION_MATCHER + "\\s*(\\S*)\\s*:\\s*(\\S*)\\s*"); 34 | 35 | /** 36 | * Format for {@code dependent -> dependency: severity}, where {@code dependent}, {@code dependency}, the arrow, 37 | * and {@code severity} are provided as arguments to {@link String#format(String, Object...) String.format}. 38 | */ 39 | private static final String ARROW_RULE_FORMAT = "%s %s %s: %s"; 40 | 41 | /** 42 | * Parses the rules in the specified string and returns a list of the created {@link DependencyRule}s. 43 | * 44 | * @param rules 45 | * a string that defines dependency rules; individual rules must be separated by a newline; empty lines are 46 | * allowed 47 | * 48 | * @return a list of parsed rules 49 | * 50 | * @throws ConfigurationException 51 | * if a non-empty line could not be parsed 52 | */ 53 | public static ImmutableList parseRules(String rules) throws ConfigurationException { 54 | requireNonNull(rules, "The argument 'rules' must not be null."); 55 | if (rules.trim().isEmpty()) 56 | return ImmutableList.of(); 57 | 58 | ImmutableList.Builder ruleList = ImmutableList.builder(); 59 | for (String ruleLine : LINE_PATTERN.split(rules)) { 60 | parseRuleLine(ruleLine).ifPresent(ruleList::add); 61 | } 62 | return ruleList.build(); 63 | } 64 | 65 | private static Optional parseRuleLine(String ruleLine) throws ConfigurationException { 66 | if (ruleLine.trim().isEmpty()) 67 | return Optional.empty(); 68 | 69 | Matcher ruleMatcher = ARROW_RULE_PATTERN.matcher(ruleLine); 70 | final Optional rule; 71 | if (ruleMatcher.find()) 72 | rule = Optional.of(extractRuleFromCurrentMatch(ruleMatcher::group)); 73 | else 74 | throw new ConfigurationException(format(ERROR_MESSAGE_LINE_INVALID_RULE, ruleLine.trim())); 75 | 76 | // disallow two rules in the same line 77 | if (ruleMatcher.find()) 78 | throw new ConfigurationException(format(ERROR_MESSAGE_MULTIPLE_RULES, ruleLine.trim())); 79 | 80 | return rule; 81 | } 82 | 83 | private static DependencyRule extractRuleFromCurrentMatch(IntFunction getGroup) 84 | throws ConfigurationException { 85 | try { 86 | return DependencyRule.of(getGroup.apply(1), getGroup.apply(3), getGroup.apply(4)); 87 | } catch (IllegalArgumentException ex) { 88 | throw new ConfigurationException(ex.getMessage()); 89 | } 90 | } 91 | 92 | /** 93 | * Formats the specified dependency rule as an arrow string that can later be parsed by this parser. 94 | * 95 | * @param arrow 96 | * the arrow text to use 97 | * @param rule 98 | * the rule to convert to string 99 | * 100 | * @return an arrow rule string 101 | */ 102 | public static String ruleToArrowString(Arrow arrow, DependencyRule rule) { 103 | requireNonNull(arrow, "The argument 'arrow' must not be null."); 104 | requireNonNull(rule, "The argument 'rule' must not be null."); 105 | 106 | return format(ARROW_RULE_FORMAT, rule.getDependent(), arrow.text(), rule.getDependency(), rule.getSeverity()); 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/mojo/DependencyRulesConfiguration.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import org.codefx.mvn.jdeps.rules.ArrowRuleParser; 4 | import org.codefx.mvn.jdeps.rules.DependencyJudge; 5 | import org.codefx.mvn.jdeps.rules.DependencyJudgeBuilder; 6 | import org.codefx.mvn.jdeps.rules.DependencyRule; 7 | import org.codefx.mvn.jdeps.rules.MapDependencyJudge.MapDependencyJudgeBuilder; 8 | import org.codefx.mvn.jdeps.rules.PackageInclusion; 9 | import org.codefx.mvn.jdeps.rules.Severity; 10 | import org.codefx.mvn.jdeps.rules.SimpleDependencyJudge; 11 | import org.codefx.mvn.jdeps.rules.XmlRule; 12 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 13 | 14 | import java.util.List; 15 | 16 | import static java.lang.String.format; 17 | import static java.util.Objects.requireNonNull; 18 | import static org.codefx.mvn.jdeps.mojo.MojoLogging.logger; 19 | 20 | /** 21 | * Captures the MOJO configuration that pertains the dependency rules and {@link #createJudge() creates} the 22 | * according {@link DependencyJudge}. 23 | */ 24 | class DependencyRulesConfiguration { 25 | 26 | /* 27 | * It looks like this class belongs into 'org.codefx.mvn.jdeps.rules' (look at the imports) but 28 | * stupid logging is in the way. I'd rather have a class that, in spirit, belongs somewhere else hang around here 29 | * than introduce Maven-specific logging into the other package. 30 | */ 31 | 32 | private final Severity defaultSeverity; 33 | private final PackageInclusion packageInclusion; 34 | private final List xml; 35 | private final List arrow; 36 | 37 | public DependencyRulesConfiguration( 38 | Severity defaultSeverity, PackageInclusion packageInclusion, List xml, List arrow) { 39 | this.defaultSeverity = requireNonNull(defaultSeverity, "The argument 'defaultSeverity' must not be null."); 40 | this.packageInclusion = requireNonNull(packageInclusion, "The argument 'packageInclusion' must not be null."); 41 | this.xml = requireNonNull(xml, "The argument 'xml' must not be null."); 42 | this.arrow = requireNonNull(arrow, "The argument 'arrow' must not be null."); 43 | } 44 | 45 | /** 46 | * @return the {@link DependencyJudge} matching the configuration 47 | */ 48 | public DependencyJudge createJudge() throws ConfigurationException { 49 | if (xml.isEmpty() && arrow.isEmpty()) 50 | return new SimpleDependencyJudge(defaultSeverity); 51 | 52 | DependencyJudgeBuilder dependencyJudgeBuilder = createBuilderFromConfiguration(); 53 | addXmlRulesToBuilder(xml, dependencyJudgeBuilder); 54 | addArrowRulesToBuilder(arrow, dependencyJudgeBuilder); 55 | return dependencyJudgeBuilder.build(); 56 | } 57 | 58 | private DependencyJudgeBuilder createBuilderFromConfiguration() { 59 | return new MapDependencyJudgeBuilder() 60 | .withInclusion(packageInclusion) 61 | .withDefaultSeverity(defaultSeverity); 62 | } 63 | 64 | static void addXmlRulesToBuilder(List xmlRules, DependencyJudgeBuilder dependencyJudgeBuilder) 65 | throws ConfigurationException { 66 | logStartAddingRules(xmlRules, "XML"); 67 | 68 | for (XmlRule rule : xmlRules) { 69 | DependencyRule dependencyRule = rule.asDependencyRule(); 70 | dependencyJudgeBuilder.addDependency(dependencyRule); 71 | logAddedRule(dependencyRule); 72 | } 73 | 74 | logDoneAddingRules(xmlRules, "XML"); 75 | } 76 | 77 | static void addArrowRulesToBuilder(List arrowRules, DependencyJudgeBuilder dependencyJudgeBuilder) 78 | throws ConfigurationException { 79 | logStartAddingRules(arrowRules, "Arrow"); 80 | 81 | // the loop can be no stream because 'ArrowRuleParser.parseRules' throws a checked exception 82 | for (String arrowRule : arrowRules) { 83 | ArrowRuleParser 84 | .parseRules(arrowRule) 85 | .forEach(rule -> { 86 | dependencyJudgeBuilder.addDependency(rule); 87 | logAddedRule(rule); 88 | }); 89 | } 90 | 91 | logDoneAddingRules(arrowRules, "Arrow"); 92 | } 93 | 94 | private static void logStartAddingRules(List rules, String ruleName) { 95 | if (!rules.isEmpty()) 96 | logger().debug("\t" + ruleName + " rules:"); 97 | } 98 | 99 | private static void logAddedRule(DependencyRule dependencyRule) { 100 | logger().debug("\t\t" + dependencyRule); 101 | } 102 | 103 | private static void logDoneAddingRules(List rules, String ruleName) { 104 | if (rules.isEmpty()) 105 | logger().debug(format("\t%s rules: none configured", ruleName)); 106 | else 107 | logger().debug(format("\ttotal: %d", rules.size())); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/MapDependencyJudge.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import com.google.common.collect.Iterables; 4 | import com.google.common.collect.Iterators; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | 10 | import static java.text.MessageFormat.format; 11 | import static java.util.Objects.requireNonNull; 12 | 13 | /** 14 | * A {@link DependencyJudge} based on a bimap {@code (dependency, dependant) -> severity} and using 15 | * {@link TypeNameHierarchy}-s to identify the best match. 16 | */ 17 | public class MapDependencyJudge implements DependencyJudge { 18 | 19 | private final PackageInclusion packageInclusion; 20 | private final Severity defaultSeverity; 21 | private final Map> dependencies; 22 | 23 | private MapDependencyJudge( 24 | PackageInclusion packageInclusion, 25 | Severity defaultSeverity, 26 | Map> dependencies) { 27 | this.packageInclusion = requireNonNull(packageInclusion, "The argument 'packageInclusion' must not be null."); 28 | this.defaultSeverity = requireNonNull(defaultSeverity, "The argument 'defaultSeverity' must not be null."); 29 | this.dependencies = requireNonNull(dependencies, "The argument 'dependencies' must not be null."); 30 | } 31 | 32 | @Override 33 | public Severity judgeSeverity(String dependentName, String dependencyName) { 34 | // the order of the two loops is crucial; 35 | // checking all dependency names before continuing with the next dependent name yields the desired behavior of 36 | // finding the best matching dependent that defines a rule for the dependency 37 | for (String dependentNamePart : namesFor(dependentName)) 38 | for (String dependencyNamePart : namesFor(dependencyName)) { 39 | Optional severity = tryGetSeverityFor(dependentNamePart, dependencyNamePart); 40 | if (severity.isPresent()) 41 | return severity.get(); 42 | } 43 | 44 | return defaultSeverity; 45 | } 46 | 47 | private Optional tryGetSeverityFor(String dependent, String dependency) { 48 | return Optional.ofNullable(dependencies.get(dependent)) 49 | .map(mapForDependent -> mapForDependent.get(dependency)); 50 | } 51 | 52 | private Iterable namesFor(String dependentName) { 53 | Iterable typeNameHierarchy = TypeNameHierarchy.forFullyQualifiedName(dependentName, packageInclusion); 54 | Iterable wildcard = () -> Iterators.singletonIterator(DependencyRule.ALL_TYPES_WILDCARD); 55 | return Iterables.concat(typeNameHierarchy, wildcard); 56 | } 57 | 58 | public static class MapDependencyJudgeBuilder implements DependencyJudgeBuilder { 59 | 60 | private PackageInclusion packageInclusion; 61 | private Severity defaultSeverity; 62 | private final Map> dependencies; 63 | private boolean alreadyBuilt; 64 | 65 | public MapDependencyJudgeBuilder() { 66 | // set default values 67 | packageInclusion = PackageInclusion.FLAT; 68 | defaultSeverity = Severity.FAIL; 69 | dependencies = new HashMap<>(); 70 | 71 | alreadyBuilt = false; 72 | } 73 | 74 | @Override 75 | public DependencyJudgeBuilder withInclusion(PackageInclusion packageInclusion) { 76 | this.packageInclusion = 77 | requireNonNull(packageInclusion, "The argument 'packageInclusion' must not be null."); 78 | return this; 79 | } 80 | 81 | @Override 82 | public DependencyJudgeBuilder withDefaultSeverity(Severity defaultSeverity) { 83 | this.defaultSeverity = requireNonNull(defaultSeverity, "The argument 'defaultSeverity' must not be null."); 84 | return this; 85 | } 86 | 87 | @Override 88 | public DependencyJudgeBuilder addDependency(DependencyRule rule) { 89 | requireNonNull(rule, "The argument 'ruleName' must not be null."); 90 | 91 | Map mapForDependent = 92 | dependencies.computeIfAbsent(rule.getDependent(), ignored -> new HashMap<>()); 93 | Severity previousSeverity = mapForDependent.put(rule.getDependency(), rule.getSeverity()); 94 | 95 | if (previousSeverity != null && previousSeverity != rule.getSeverity()) { 96 | String message = format( 97 | "The dependency '{0} -> {1}' is defined with multiple severitues {2} and {3}.", 98 | rule.getDependent(), rule.getDependency(), previousSeverity, rule.getSeverity()); 99 | throw new IllegalArgumentException(message); 100 | } 101 | 102 | return this; 103 | } 104 | 105 | public DependencyJudge build() { 106 | if (alreadyBuilt) 107 | throw new IllegalStateException("A builder can only be used once."); 108 | alreadyBuilt = true; 109 | return new MapDependencyJudge(packageInclusion, defaultSeverity, dependencies); 110 | } 111 | 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/tool/LineWriter.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.tool; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | 5 | import java.io.BufferedWriter; 6 | import java.io.IOException; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.StandardOpenOption; 10 | import java.util.stream.Stream; 11 | 12 | import static java.lang.String.format; 13 | import static java.nio.file.StandardOpenOption.CREATE; 14 | import static java.nio.file.StandardOpenOption.WRITE; 15 | import static java.util.Objects.requireNonNull; 16 | 17 | /** 18 | * Writes a stream of lines to a file. 19 | */ 20 | public class LineWriter { 21 | 22 | private final Path outputFile; 23 | private final IfFileExists ifFileExists; 24 | private final StaticContent staticContent; 25 | 26 | /** 27 | * Creates a new writer. 28 | * 29 | * @param outputFile 30 | * the file to write to 31 | * @param ifFileExists 32 | * what to do if the file already exists 33 | * @param staticContent 34 | * the content to wrap the written lines in 35 | */ 36 | public LineWriter(Path outputFile, IfFileExists ifFileExists, StaticContent staticContent) { 37 | this.outputFile = requireNonNull(outputFile, "The argument 'outputFile' must not be null."); 38 | this.ifFileExists = requireNonNull(ifFileExists, "The argument 'ifFileExists' must not be null."); 39 | this.staticContent = requireNonNull(staticContent, "The argument 'staticContent' must not be null."); 40 | } 41 | 42 | /** 43 | * Writes the specified lines to the file. 44 | * 45 | * @param lines 46 | * the lines to write 47 | * 48 | * @throws IOException 49 | * if writing fails 50 | */ 51 | public void write(Stream lines) throws IOException { 52 | try (BufferedWriter writer = openFile()) { 53 | staticContent.prolog.forEach(line -> writeToFile(writer, line)); 54 | lines.forEachOrdered(line -> writeToFile(writer, staticContent.indent + line)); 55 | staticContent.epilog.forEach(line -> writeToFile(writer, line)); 56 | } catch (IllegalStateException ex) { 57 | // 'IOException's are rethrown as 'IllegalStateExceptions' 58 | if (ex.getCause() instanceof IOException) 59 | throwWriteFailedException((IOException) ex.getCause()); 60 | } catch (IOException ex) { 61 | throwWriteFailedException(ex); 62 | } 63 | } 64 | 65 | private BufferedWriter openFile() throws IOException { 66 | // create a new file or append the existing file; open with write access 67 | return Files.newBufferedWriter(outputFile, CREATE, ifFileExists.openOption(), WRITE); 68 | } 69 | 70 | private void writeToFile(BufferedWriter writer, String line) { 71 | try { 72 | writer.append(line); 73 | writer.newLine(); 74 | } catch (IOException ex) { 75 | throw new IllegalStateException(ex); 76 | } 77 | } 78 | 79 | private void throwWriteFailedException(IOException ex) throws IOException { 80 | String message = format("Writing to '%s' failed.", outputFile); 81 | throw new IOException(message, ex); 82 | } 83 | 84 | /** 85 | * Defines some static content the writer will write to the file. 86 | */ 87 | public static class StaticContent { 88 | 89 | public final ImmutableList prolog; 90 | public final ImmutableList epilog; 91 | public final String indent; 92 | 93 | /** 94 | * @param prolog 95 | * the written lines must start with these lines 96 | * @param epilog 97 | * the written lines must end with these lines 98 | * @param indent 99 | * this indent is to be added before each line from the stream; it must only consist of whitespace 100 | */ 101 | public StaticContent(ImmutableList prolog, ImmutableList epilog, String indent) { 102 | this.prolog = requireNonNull(prolog, "The argument 'prolog' must not be null."); 103 | this.epilog = requireNonNull(epilog, "The argument 'epilog' must not be null."); 104 | this.indent = requireNonNull(indent, "The argument 'indent' must not be null."); 105 | if (!indent.trim().isEmpty()) 106 | throw new IllegalArgumentException("The argument 'indent' must only consist of whitespace."); 107 | } 108 | } 109 | 110 | /** 111 | * Defines the writers behavior if the file already exists. 112 | */ 113 | public enum IfFileExists { 114 | 115 | /** 116 | * Remove all existing content. 117 | */ 118 | REMOVE_EXISTING_CONTENT, 119 | 120 | /** 121 | * Append the new content. 122 | */ 123 | APPEND_NEW_CONTENT; 124 | 125 | public StandardOpenOption openOption() { 126 | switch (this) { 127 | case REMOVE_EXISTING_CONTENT: 128 | return StandardOpenOption.TRUNCATE_EXISTING; 129 | case APPEND_NEW_CONTENT: 130 | return StandardOpenOption.APPEND; 131 | default: 132 | throw new IllegalArgumentException(format("Unknown IfFileExists.%s.", this)); 133 | } 134 | } 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/parse/ViolationParserTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.parse; 2 | 3 | import org.codefx.mvn.jdeps.dependency.InternalType; 4 | import org.codefx.mvn.jdeps.dependency.Type; 5 | import org.codefx.mvn.jdeps.dependency.Violation; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.mockito.ArgumentCaptor; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.StringReader; 12 | import java.util.function.Consumer; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | import static org.mockito.Mockito.mock; 16 | import static org.mockito.Mockito.verify; 17 | import static org.mockito.Mockito.verifyZeroInteractions; 18 | 19 | /** 20 | * Tests the class {@link ViolationParser}. 21 | */ 22 | public class ViolationParserTest { 23 | 24 | private ViolationParser parser; 25 | 26 | private Consumer violationVerifier; 27 | 28 | @Before 29 | public void setup() { 30 | violationVerifier = mock(Consumer.class); 31 | parser = new ViolationParser(violationVerifier); 32 | } 33 | 34 | // isInternalTypeLine 35 | 36 | @Test(expected = NullPointerException.class) 37 | public void parseLine_nullLine_throwsNullPointerException() throws Exception { 38 | parser.parseLine(null); 39 | } 40 | 41 | @Test 42 | public void isViolation_emptyLine_createsNoViolation() throws Exception { 43 | parser.parseLine(""); 44 | 45 | verifyZeroInteractions(violationVerifier); 46 | } 47 | 48 | @Test(expected = IllegalStateException.class) 49 | public void isViolation_onlyDependent_throwsIllegalStateException() throws Exception { 50 | String onlyDependent = " org.codefx.lab.App (target)\n"; 51 | 52 | parseBlock(parser, onlyDependent); 53 | } 54 | 55 | @Test 56 | public void parseLine_matchingBlock$1_createsViolation() throws Exception { 57 | String matchingBlock = "" 58 | + " org.codefx.lab.App (target)\n" 59 | + " -> sun.misc.BASE64Decoder JDK internal API (rt.jar)\n" 60 | + " -> sun.misc.Unsafe JDK internal API (rt.jar)\n"; 61 | ArgumentCaptor violationCaptor = ArgumentCaptor.forClass(Violation.class); 62 | 63 | parseBlock(parser, matchingBlock); 64 | 65 | verify(violationVerifier).accept(violationCaptor.capture()); 66 | Violation violation = violationCaptor.getValue(); 67 | assertThat(violation.getDependent()).isEqualTo(Type.of("org.codefx.lab", "App")); 68 | assertThat(violation.getInternalDependencies()) 69 | .hasSize(2) 70 | .contains(InternalType.of("sun.misc", "BASE64Decoder", "JDK internal API", "rt.jar")) 71 | .contains(InternalType.of("sun.misc", "Unsafe", "JDK internal API", "rt.jar")); 72 | } 73 | 74 | @Test 75 | public void parseLine_matchingBlockWithCapitalLettersInDependendent_createsViolation() throws Exception { 76 | String matchingBlockWithCapitalLettersInDependent = "" 77 | + " Org.codefx.lAb.App (target)\n" 78 | + " -> sun.misc.BASE64Decoder JDK internal API (rt.jar)\n" 79 | + " -> sun.misc.Unsafe JDK internal API (rt.jar)\n"; 80 | ArgumentCaptor violationCaptor = ArgumentCaptor.forClass(Violation.class); 81 | 82 | parseBlock(parser, matchingBlockWithCapitalLettersInDependent); 83 | 84 | verify(violationVerifier).accept(violationCaptor.capture()); 85 | Violation violation = violationCaptor.getValue(); 86 | assertThat(violation.getDependent()).isEqualTo(Type.of("Org.codefx.lAb", "App")); 87 | } 88 | 89 | @Test 90 | public void parseLine_matchingBlockWithCapitalLettersInDependencies_createsViolation() throws Exception { 91 | String matchingBlockWithCapitalLettersInDependencies = "" 92 | + " org.codefx.lab.App (target)\n" 93 | + " -> Sun.misc.BASE64Decoder JDK internal API (rt.jar)\n" 94 | + " -> sun.miSc.Unsafe JDK internal API (rt.jar)\n"; 95 | ArgumentCaptor violationCaptor = ArgumentCaptor.forClass(Violation.class); 96 | 97 | parseBlock(parser, matchingBlockWithCapitalLettersInDependencies); 98 | 99 | verify(violationVerifier).accept(violationCaptor.capture()); 100 | Violation violation = violationCaptor.getValue(); 101 | assertThat(violation.getInternalDependencies()) 102 | .hasSize(2) 103 | .contains(InternalType.of("Sun.misc", "BASE64Decoder", "JDK internal API", "rt.jar")) 104 | .contains(InternalType.of("sun.miSc", "Unsafe", "JDK internal API", "rt.jar")); 105 | } 106 | 107 | private static void parseBlock(ViolationParser parser, String block) { 108 | new BufferedReader(new StringReader(block)) 109 | .lines() 110 | .forEach(parser::parseLine); 111 | parser.finish(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/mojo/DependencyRulesConfigurationTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.mojo; 2 | 3 | import org.codefx.mvn.jdeps.rules.DependencyJudgeBuilder; 4 | import org.codefx.mvn.jdeps.rules.DependencyRule; 5 | import org.codefx.mvn.jdeps.rules.Severity; 6 | import org.codefx.mvn.jdeps.rules.XmlRule; 7 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static java.util.Collections.singletonList; 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.junit.Assert.fail; 17 | import static org.mockito.Mockito.mock; 18 | import static org.mockito.Mockito.verify; 19 | import static org.mockito.Mockito.verifyNoMoreInteractions; 20 | import static org.mockito.Mockito.verifyZeroInteractions; 21 | 22 | /** 23 | * Tests {@link DependencyRulesConfiguration}. 24 | */ 25 | public class DependencyRulesConfigurationTest { 26 | 27 | private DependencyJudgeBuilder dependencyJudgeBuilder; 28 | 29 | @Before 30 | public void createDependencyJudgeBuilder() { 31 | dependencyJudgeBuilder = mock(DependencyJudgeBuilder.class); 32 | } 33 | 34 | // #begin XML RULES 35 | 36 | @Test 37 | public void addXmlRulesToBuilder_invalidRule_ruleNotAddedToBuilder() { 38 | // see 'DependencyRuleTest' for more details of how rules can be invalid 39 | XmlRule invalidRule = new XmlRule("", "", Severity.FAIL); 40 | 41 | try { 42 | DependencyRulesConfiguration.addXmlRulesToBuilder(singletonList(invalidRule), dependencyJudgeBuilder); 43 | fail(); 44 | } catch (ConfigurationException ex) { 45 | assertThat(ex).hasMessageContaining("The rule ( -> : FAIL)"); 46 | } 47 | 48 | verifyZeroInteractions(dependencyJudgeBuilder); 49 | } 50 | 51 | @Test 52 | public void addXmlRulesToBuilder_validRule_ruleAddedToBuilder() throws Exception { 53 | XmlRule validXmlRule = new XmlRule("com.foo.bar", "sun.misc.Unsafe", Severity.FAIL); 54 | DependencyRule validRule = validXmlRule.asDependencyRule(); 55 | 56 | DependencyRulesConfiguration.addXmlRulesToBuilder(singletonList(validXmlRule), dependencyJudgeBuilder); 57 | verify(dependencyJudgeBuilder).addDependency(validRule); 58 | verifyNoMoreInteractions(dependencyJudgeBuilder); 59 | } 60 | 61 | @Test 62 | public void addXmlRulesToBuilder_validRules_rulesAddedToBuilder() throws Exception { 63 | List xmlRules = Arrays.asList( 64 | new XmlRule("com.foo.bar", "sun.misc.Unsafe", Severity.FAIL), 65 | new XmlRule("com.foo", "sun.misc.Unsafe", Severity.WARN), 66 | new XmlRule("com", "sun.misc.Unsafe", Severity.INFORM)); 67 | 68 | DependencyRulesConfiguration.addXmlRulesToBuilder(xmlRules, dependencyJudgeBuilder); 69 | verify(dependencyJudgeBuilder).addDependency(xmlRules.get(0).asDependencyRule()); 70 | verify(dependencyJudgeBuilder).addDependency(xmlRules.get(1).asDependencyRule()); 71 | verify(dependencyJudgeBuilder).addDependency(xmlRules.get(2).asDependencyRule()); 72 | verifyNoMoreInteractions(dependencyJudgeBuilder); 73 | } 74 | 75 | // #end XML RULES 76 | 77 | // #begin ARROW RULES 78 | 79 | @Test 80 | public void addArrowRulesToBuilder_invalidRule_ruleNotAddedToBuilder() { 81 | // see 'DependencyRuleTest' for more details of how rules can be invalid 82 | String invalidRule = "INVALID RULE"; 83 | 84 | try { 85 | DependencyRulesConfiguration.addArrowRulesToBuilder(singletonList(invalidRule), dependencyJudgeBuilder); 86 | fail(); 87 | } catch (ConfigurationException ex) { 88 | assertThat(ex).hasMessageContaining("The line 'INVALID RULE' defines no valid rule."); 89 | } 90 | 91 | verifyZeroInteractions(dependencyJudgeBuilder); 92 | } 93 | 94 | @Test 95 | public void addArrowRulesToBuilder_validRule_ruleAddedToBuilder() throws Exception { 96 | String validArrowRule = "com.foo.bar -> sun.misc.Unsafe: FAIL"; 97 | DependencyRule validRule = DependencyRule.of("com.foo.bar", "sun.misc.Unsafe", Severity.FAIL); 98 | 99 | DependencyRulesConfiguration.addArrowRulesToBuilder(singletonList(validArrowRule), dependencyJudgeBuilder); 100 | verify(dependencyJudgeBuilder).addDependency(validRule); 101 | verifyNoMoreInteractions(dependencyJudgeBuilder); 102 | } 103 | 104 | @Test 105 | public void addArrowRulesToBuilder_validRules_rulesAddedToBuilder() throws Exception { 106 | List arrowRules = Arrays.asList( 107 | "com.foo.bar -> sun.misc.Unsafe: FAIL", 108 | "com.foo -> sun.misc.Unsafe: WARN", 109 | "com -> sun.misc.Unsafe: INFORM"); 110 | List rules = Arrays.asList( 111 | DependencyRule.of("com.foo.bar", "sun.misc.Unsafe", Severity.FAIL), 112 | DependencyRule.of("com.foo", "sun.misc.Unsafe", Severity.WARN), 113 | DependencyRule.of("com", "sun.misc.Unsafe", Severity.INFORM)); 114 | 115 | DependencyRulesConfiguration.addArrowRulesToBuilder(arrowRules, dependencyJudgeBuilder); 116 | verify(dependencyJudgeBuilder).addDependency(rules.get(0)); 117 | verify(dependencyJudgeBuilder).addDependency(rules.get(1)); 118 | verify(dependencyJudgeBuilder).addDependency(rules.get(2)); 119 | verifyNoMoreInteractions(dependencyJudgeBuilder); 120 | } 121 | 122 | // #end ARROW RULES 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/result/AnnotatedViolationTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.result; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.codefx.mvn.jdeps.dependency.InternalType; 5 | import org.codefx.mvn.jdeps.dependency.Type; 6 | import org.codefx.mvn.jdeps.dependency.Violation; 7 | import org.codefx.mvn.jdeps.rules.Severity; 8 | import org.junit.Test; 9 | 10 | import java.util.Arrays; 11 | import java.util.Optional; 12 | import java.util.stream.Stream; 13 | 14 | import static org.assertj.core.api.Assertions.assertThat; 15 | 16 | /** 17 | * Tests {@link AnnotatedViolation}. 18 | */ 19 | public class AnnotatedViolationTest { 20 | 21 | private static final Type DEPENDENT = Type.of("com.foo.Bar"); 22 | 23 | private static final InternalType ENCODER = InternalType.of("sun.misc", "BASE64Encoder", "", ""); 24 | private static final InternalType DECODER = InternalType.of("sun.misc", "BASE64Decoder", "", ""); 25 | private static final InternalType UNSAFE = InternalType.of("sun.misc", "Unsafe", "", ""); 26 | 27 | private static final AnnotatedInternalType INFORMED_ENCODER = AnnotatedInternalType.of(ENCODER, Severity.INFORM); 28 | private static final AnnotatedInternalType WARNED_DECODER = AnnotatedInternalType.of(DECODER, Severity.WARN); 29 | private static final AnnotatedInternalType FAILED_UNSAFE = AnnotatedInternalType.of(UNSAFE, Severity.FAIL); 30 | 31 | private static final ImmutableList ALL_ANNOTATED_TYPES = 32 | ImmutableList.of(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE); 33 | 34 | @Test(expected = NullPointerException.class) 35 | public void of_dependentNull_throwsException() { 36 | AnnotatedViolation.of(null, ALL_ANNOTATED_TYPES); 37 | } 38 | 39 | @Test(expected = NullPointerException.class) 40 | public void of_dependenciesNull_throwsException() { 41 | AnnotatedViolation.of(DEPENDENT, null); 42 | } 43 | 44 | @Test(expected = IllegalArgumentException.class) 45 | public void of_dependenciesEmpty_throwsException() { 46 | AnnotatedViolation.of(DEPENDENT, ImmutableList.of()); 47 | } 48 | 49 | @Test 50 | public void only_dependentOfViolations_returnsDependentSpecifiedDuringConstruction() throws Exception { 51 | AnnotatedViolation annotatedViolation = AnnotatedViolation.of(DEPENDENT, ALL_ANNOTATED_TYPES); 52 | streamViolationsForAllSeverities(annotatedViolation) 53 | .map(Violation::getDependent) 54 | .forEach(dependent -> assertThat(dependent).isEqualTo(DEPENDENT)); 55 | } 56 | 57 | private static Stream streamViolationsForAllSeverities(AnnotatedViolation violation) { 58 | return Arrays 59 | .stream(Severity.values()) 60 | .map(violation::only) 61 | .filter(Optional::isPresent) 62 | .map(Optional::get); 63 | } 64 | 65 | @Test 66 | public void only_severityExistsNot_returnsEmptyOptional() throws Exception { 67 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 68 | .only(Severity.IGNORE)) 69 | .isEmpty(); 70 | assertThat(violationWithDependencies(WARNED_DECODER, FAILED_UNSAFE) 71 | .only(Severity.INFORM)) 72 | .isEmpty(); 73 | assertThat(violationWithDependencies(INFORMED_ENCODER, FAILED_UNSAFE) 74 | .only(Severity.WARN)) 75 | .isEmpty(); 76 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER) 77 | .only(Severity.FAIL)) 78 | .isEmpty(); 79 | } 80 | 81 | @Test 82 | public void only_severityExists_returnsExactlyInternalDependenciesForSeverity() throws Exception { 83 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 84 | .only(Severity.INFORM).get() 85 | .getInternalDependencies()) 86 | .containsOnly(ENCODER); 87 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 88 | .only(Severity.WARN).get() 89 | .getInternalDependencies()) 90 | .containsOnly(DECODER); 91 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 92 | .only(Severity.FAIL).get() 93 | .getInternalDependencies()) 94 | .containsOnly(UNSAFE); 95 | } 96 | 97 | @Test 98 | public void except_noOtherSeverityExists_returnsEmptyOptional() throws Exception { 99 | assertThat(violationWithDependencies(INFORMED_ENCODER) 100 | .except(Severity.IGNORE, Severity.INFORM)) 101 | .isEmpty(); 102 | assertThat(violationWithDependencies(WARNED_DECODER) 103 | .except(Severity.IGNORE, Severity.WARN)) 104 | .isEmpty(); 105 | assertThat(violationWithDependencies(FAILED_UNSAFE) 106 | .except(Severity.IGNORE, Severity.FAIL)) 107 | .isEmpty(); 108 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER) 109 | .except(Severity.IGNORE, Severity.INFORM, Severity.WARN)) 110 | .isEmpty(); 111 | } 112 | 113 | @Test 114 | public void except_otherSeveritiesExist_returnsExactlyInternalDependenciesForOtherSeverities() throws Exception { 115 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 116 | .except(Severity.INFORM).get() 117 | .getInternalDependencies()) 118 | .containsOnly(DECODER, UNSAFE); 119 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 120 | .except(Severity.WARN).get() 121 | .getInternalDependencies()) 122 | .containsOnly(ENCODER, UNSAFE); 123 | assertThat(violationWithDependencies(INFORMED_ENCODER, WARNED_DECODER, FAILED_UNSAFE) 124 | .except(Severity.FAIL).get() 125 | .getInternalDependencies()) 126 | .containsOnly(ENCODER, DECODER); 127 | } 128 | 129 | private static AnnotatedViolation violationWithDependencies(AnnotatedInternalType... dependencies) { 130 | return AnnotatedViolation.of(DEPENDENT, ImmutableList.copyOf(dependencies)); 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/DependencyRule.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import java.util.Objects; 4 | 5 | import static java.lang.String.format; 6 | 7 | public final class DependencyRule { 8 | 9 | public static final String ALL_TYPES_WILDCARD = "*"; 10 | 11 | private static final String ERROR_MESSAGE_MISSING_TYPE = "The rule %s defines no %s."; 12 | private static final String ERROR_MESSAGE_MISSING_SEVERITY = "The rule %s defines no severity."; 13 | 14 | private static final String ERROR_MESSAGE_NAME_PART_EMPTY = 15 | "In the rule %s the name '%s' contains one empty part. Make sure it has no superfluous dots."; 16 | private static final String ERROR_MESSAGE_NAME_PART_STARTS_INVALID = 17 | "In the rule %s a part of the name '%s' starts with the invalid character '%s'."; 18 | private static final String ERROR_MESSAGE_NAME_PART_CONTAINS_INVALID = 19 | "In the rule %s the name '%s' contains the invalid character '%s'."; 20 | 21 | private static final String ERROR_MESSAGE_INVALID_SEVERITY = "The rule %s defines an invalid severity."; 22 | 23 | private final String dependent; 24 | private final String dependency; 25 | private final Severity severity; 26 | 27 | private DependencyRule(String dependent, String dependency, Severity severity) { 28 | this.dependent = dependent; 29 | this.dependency = dependency; 30 | this.severity = severity; 31 | } 32 | 33 | public static DependencyRule of(String dependent, String dependency, String severity) { 34 | Severity asSeverity = parseSeverity(dependent, dependency, severity); 35 | return of(dependent, dependency, asSeverity); 36 | } 37 | 38 | private static Severity parseSeverity(String dependent, String dependency, String severity) { 39 | if (severity == null) 40 | throw new IllegalArgumentException( 41 | format(ERROR_MESSAGE_MISSING_SEVERITY, toString(dependent, dependency, (String) null))); 42 | try { 43 | return Severity.valueOf(severity); 44 | } catch (IllegalArgumentException ex) { 45 | throw new IllegalArgumentException( 46 | format(ERROR_MESSAGE_INVALID_SEVERITY, toString(dependent, dependency, severity))); 47 | } 48 | } 49 | 50 | public static DependencyRule of(String dependent, String dependency, Severity severity) { 51 | checkName(dependent, toString(dependent, dependency, severity), "dependent"); 52 | checkName(dependency, toString(dependent, dependency, severity), "dependency"); 53 | if (severity == null) 54 | throw new IllegalArgumentException( 55 | format(ERROR_MESSAGE_MISSING_SEVERITY, toString(dependent, dependency, (String) null))); 56 | 57 | return new DependencyRule(dependent, dependency, severity); 58 | } 59 | 60 | /* 61 | * If only 'checkValidity()' were accessible, we would have to have the same checks for the dependent and the 62 | * dependency. To prevent this useless repetition 'DependencyRule' exposes 'checkName'. 63 | */ 64 | 65 | /** 66 | * Checks whether the specified name is a valid Java identifier for a package or class. 67 | * 68 | * @param name 69 | * the name to check 70 | * @param ruleAsString 71 | * a textual representation of the rule so that it can be used in the error output 72 | * @param role 73 | * the role of the type called {@code name}, i.e. dependent or dependency 74 | * 75 | * @throws IllegalArgumentException 76 | * if the name is invalid 77 | */ 78 | static void checkName(String name, String ruleAsString, String role) throws IllegalArgumentException { 79 | if (name == null || name.isEmpty()) 80 | throw new IllegalArgumentException(format(ERROR_MESSAGE_MISSING_TYPE, ruleAsString, role)); 81 | 82 | if (name.equals(ALL_TYPES_WILDCARD)) 83 | return; 84 | 85 | for (String namePart : name.split("\\.")) { 86 | if (namePart == null || namePart.isEmpty()) 87 | throw new IllegalArgumentException( 88 | format(ERROR_MESSAGE_NAME_PART_EMPTY, ruleAsString, name)); 89 | if (!Character.isJavaIdentifierStart(namePart.charAt(0))) 90 | throw new IllegalArgumentException( 91 | format(ERROR_MESSAGE_NAME_PART_STARTS_INVALID, ruleAsString, name, namePart.charAt(0))); 92 | for (int i = 1; i < namePart.length(); i++) 93 | if (!Character.isJavaIdentifierPart(namePart.charAt(i))) 94 | throw new IllegalArgumentException( 95 | format(ERROR_MESSAGE_NAME_PART_CONTAINS_INVALID, ruleAsString, name, namePart.charAt(i))); 96 | } 97 | 98 | // split does not include trailing empty strings so make an extra check for string ending with "." 99 | if (name.endsWith(".")) 100 | throw new IllegalArgumentException( 101 | format(ERROR_MESSAGE_NAME_PART_EMPTY, ruleAsString, name)); 102 | } 103 | 104 | public String getDependent() { 105 | return dependent; 106 | } 107 | 108 | public String getDependency() { 109 | return dependency; 110 | } 111 | 112 | public Severity getSeverity() { 113 | return severity; 114 | } 115 | 116 | @Override 117 | public boolean equals(Object o) { 118 | if (this == o) 119 | return true; 120 | if (!(o instanceof DependencyRule)) 121 | return false; 122 | DependencyRule that = (DependencyRule) o; 123 | return Objects.equals(dependent, that.dependent) 124 | && Objects.equals(dependency, that.dependency); 125 | } 126 | 127 | @Override 128 | public int hashCode() { 129 | return Objects.hash(dependent, dependency); 130 | } 131 | 132 | @Override 133 | public String toString() { 134 | return toString(dependent, dependency, severity); 135 | } 136 | 137 | private static String toString(String dependent, String dependency, Severity severity) { 138 | return toString(dependent, dependency, severity == null ? "null" : severity.toString()); 139 | } 140 | 141 | private static String toString(String dependent, String dependency, String severity) { 142 | return format("(%s -> %s: %s)", dependent, dependency, severity); 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/dependency/ViolationTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.dependency; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.junit.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | /** 9 | * Tests {@link Violation}. 10 | */ 11 | public class ViolationTest { 12 | 13 | private static final Type DEPENDENT = Type.of("com.foo.Bar"); 14 | 15 | private static final ImmutableList DEPENDENCIES = 16 | ImmutableList.of( 17 | InternalType.of("sun.misc", "Unsafe", "", ""), 18 | InternalType.of("sun.misc", "BASE64Decoder", "", ""), 19 | InternalType.of("sun.misc", "BASE64Encoder", "", "") 20 | ); 21 | 22 | private static final ImmutableList SORTED_DEPENDENCIES = 23 | ImmutableList.of( 24 | InternalType.of("sun.misc", "BASE64Decoder", "", ""), 25 | InternalType.of("sun.misc", "BASE64Encoder", "", ""), 26 | InternalType.of("sun.misc", "Unsafe", "", "") 27 | ); 28 | 29 | // #begin BUILD FOR 30 | 31 | @Test(expected = NullPointerException.class) 32 | public void buildFor_dependentNull_throwsException() { 33 | Violation.buildFor(null, DEPENDENCIES); 34 | } 35 | 36 | @Test(expected = NullPointerException.class) 37 | public void buildFor_dependenciesNull_throwsException() { 38 | Violation.buildFor(DEPENDENT, null); 39 | } 40 | 41 | @Test(expected = IllegalArgumentException.class) 42 | public void buildFor_dependenciesEmpty_throwsException() { 43 | Violation.buildFor(DEPENDENT, ImmutableList.of()); 44 | } 45 | 46 | @Test 47 | public void buildFor_dependent_violationReturnsDependent() throws Exception { 48 | Violation violation = Violation.buildFor(DEPENDENT, DEPENDENCIES); 49 | assertThat(violation.getDependent()).isEqualTo(DEPENDENT); 50 | } 51 | 52 | @Test 53 | public void buildFor_dependencies_violationReturnsInternalDependenciesInSortedOrder() throws Exception { 54 | Violation violation = Violation.buildFor(DEPENDENT, DEPENDENCIES); 55 | assertThat(violation.getInternalDependencies()).containsExactlyElementsOf(SORTED_DEPENDENCIES); 56 | } 57 | 58 | // #end BUILD FOR 59 | 60 | // #begin BUILD FOR DEPENDENT 61 | 62 | @Test(expected = NullPointerException.class) 63 | public void buildForDependent_dependentNull_throwsException() { 64 | Violation.buildForDependent(null); 65 | } 66 | 67 | @Test 68 | public void buildForDependent_buildViolation_returnsDependent() { 69 | Violation violation = Violation 70 | .buildForDependent(DEPENDENT) 71 | .addDependencies(DEPENDENCIES) 72 | .build(); 73 | assertThat(violation.getDependent()).isEqualTo(DEPENDENT); 74 | } 75 | 76 | @Test(expected = NullPointerException.class) 77 | public void addDependency_dependencyNull_throwsException() { 78 | Violation.buildForDependent(DEPENDENT).addDependency(null); 79 | } 80 | 81 | @Test 82 | public void addDependency_buildViolation_returnsAddedDependency() throws Exception { 83 | Violation violation = Violation 84 | .buildForDependent(DEPENDENT) 85 | .addDependency(DEPENDENCIES.get(0)) 86 | .build(); 87 | assertThat(violation.getInternalDependencies()).containsExactly(DEPENDENCIES.get(0)); 88 | } 89 | 90 | @Test(expected = NullPointerException.class) 91 | public void addDependencies_dependencyNull_throwsException() { 92 | Violation.buildForDependent(DEPENDENT).addDependencies((Iterable) null); 93 | } 94 | 95 | @Test 96 | public void addDependencies_buildViolation_returnsAddedDependenciesInSortedOrder() throws Exception { 97 | Violation violation = Violation 98 | .buildForDependent(DEPENDENT) 99 | .addDependencies(DEPENDENCIES) 100 | .build(); 101 | assertThat(violation.getInternalDependencies()).containsExactlyElementsOf(SORTED_DEPENDENCIES); 102 | } 103 | 104 | // #end BUILD FOR DEPENDENT 105 | 106 | @Test 107 | public void compareTo_differentDependents_orderedByDependents() throws Exception { 108 | Violation smaller = Violation 109 | .buildForDependent(Type.of("java.lang.Object")) 110 | .addDependencies(DEPENDENCIES) 111 | .build(); 112 | Violation greater = Violation 113 | .buildForDependent(Type.of("java.lang.String")) 114 | .addDependencies(DEPENDENCIES) 115 | .build(); 116 | 117 | assertThat(smaller.compareTo(greater)).isNegative(); 118 | assertThat(greater.compareTo(smaller)).isPositive(); 119 | } 120 | 121 | @Test 122 | public void compareTo_sameDependents_differentDependencies_orderedByDependencies() throws Exception { 123 | Violation smaller = Violation 124 | .buildForDependent(DEPENDENT) 125 | .addDependencies( 126 | InternalType.of("sun.misc", "BASE64Decoder", "", ""), 127 | InternalType.of("sun.misc", "Unsafe", "", "")) 128 | .build(); 129 | Violation greater = Violation 130 | .buildForDependent(DEPENDENT) 131 | .addDependencies( 132 | InternalType.of("sun.misc", "BASE64Encoder", "", ""), 133 | InternalType.of("sun.misc", "Unsafe", "", "")) 134 | .build(); 135 | 136 | assertThat(smaller.compareTo(greater)).isNegative(); 137 | assertThat(greater.compareTo(smaller)).isPositive(); 138 | } 139 | 140 | @Test 141 | public void compareTo_sameDependents_subsetDependencies_orderedByDependencies() throws Exception { 142 | Violation smaller = Violation 143 | .buildForDependent(DEPENDENT) 144 | .addDependencies( 145 | InternalType.of("sun.misc", "BASE64Decoder", "", ""), 146 | InternalType.of("sun.misc", "BASE64Encoder", "", "")) 147 | .build(); 148 | Violation greater = Violation 149 | .buildForDependent(DEPENDENT) 150 | .addDependencies( 151 | InternalType.of("sun.misc", "BASE64Decoder", "", ""), 152 | InternalType.of("sun.misc", "BASE64Encoder", "", ""), 153 | InternalType.of("sun.misc", "Unsafe", "", "")) 154 | .build(); 155 | 156 | assertThat(smaller.compareTo(greater)).isNegative(); 157 | assertThat(greater.compareTo(smaller)).isPositive(); 158 | } 159 | 160 | @Test 161 | public void compareTo_sameDependents_sameDependencies_orderedSame() throws Exception { 162 | Violation one = Violation 163 | .buildForDependent(DEPENDENT) 164 | .addDependencies(DEPENDENCIES) 165 | .build(); 166 | Violation other = Violation 167 | .buildForDependent(DEPENDENT) 168 | .addDependencies(DEPENDENCIES) 169 | .build(); 170 | 171 | assertThat(one.compareTo(other)).isZero(); 172 | assertThat(other.compareTo(one)).isZero(); 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/rules/DependencyJudge.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.codefx.mvn.jdeps.dependency.Type; 4 | 5 | /** 6 | * Judges the severity of individual dependencies according to predefined rules. 7 | * 8 | *

Rules

9 | * Rules are specified during construction and generally take the form {@code (dependant -> dependency; severity)}. 10 | * The {@code dependent} as well as the {@code dependency} can be either a type or a package. 11 | *

12 | * Given a pair of types, a rule is said to match if the first type belongs to its dependent and the second 13 | * type belongs to its dependency. "Belongs to" has a different meaning depending on whether the judge is "flat" or 14 | * "hierarchical" - see Hierarchical vs. Flat for details and examples - but a type always "belongs to" itself 15 | * and the package that contains it. 16 | *

17 | * The best match for a pair of types is the rule that has the most specific dependant that contains the first 18 | * type and out of those partial matches defines the most specific dependency that contains the second. 19 | *

20 | * Calling the judge with a pair of types returns either the severity specified by the best matching rule or, if no 21 | * rule matches, the default severity specified during construction. 22 | * 23 | *

Example

24 | * Given the rules {@code A (com.foo -> sun.misc.Unsafe; WARN)}, {@code B (com.foo -> java.lang; WARN)}, and 25 | * {@code C (com.foo.Bar -> sun.misc; WARN)}: 26 | *
    27 | *
  • for {@code com.foo.Foo -> sun.misc.Unsafe} the only match will be {@code A} because {@code Foo} does not belong 28 | * to {@code C}'s {@code Bar} and {@code Unsafe} does not belong to {@code B}'s {@code java.lang} 29 | *
  • for {@code com.foo.Bar -> sun.misc.Unsafe} the best match will be {@code C} because it has the most 30 | * specific dependent of the three rules and its dependency still matches {@code Unsafe} 31 | *
  • for {@code com.foo.Bar -> java.lang.String} the best match will be {@code B} because even though {@code C} 32 | * defines a more specific dependant (an exact match to be precise) it does not define a matching dependency 33 | * so it does not match as a whole 34 | *
35 | * 36 | *

Hierarchical vs. Flat

37 | * 38 | *

Flat Judge

39 | * A {@link PackageInclusion#FLAT flat} judge adheres to the official interpretation of Java packages, which are not hierarchical. 40 | * In this case there is no relation between, e.g., the packages "com.foo" and "com.foo.bar". 41 | *

42 | * Rules defined for a package will only match the contained types, including their inner types. Rules defined for 43 | * types will match these types and the inner types. 44 | * 45 | *

Example

46 | * Given the rule {@code (com.foo.Bar -> sun.misc.Unsafe; WARN)}: 47 | *
    48 | *
  • {@code com.foo.Bar -> sun.misc.Unsafe} will match 49 | *
  • {@code com.foo.Bar.InnerClass -> sun.misc.Unsafe} will match because {@code InnerClass} belongs to {@code Bar} 50 | *
  • {@code com.foo.Baz -> sun.misc.Unsafe} will not match because {@code Baz} is not {@code Bar} 51 | *
  • {@code com.foo.Bar -> sun.misc.BASE64Decoder} will not match because {@code BASE64Decoder} is not {@code Unsafe} 52 | *
53 | * Given the rule {@code (com.foo -> sun.misc.Unsafe; WARN)}: 54 | *
    55 | *
  • {@code com.foo.Bar -> sun.misc.Unsafe} will match because {@code Bar} is in the correct package 56 | *
  • {@code com.foo.Bar.InnerClass -> sun.misc.Unsafe} will match because {@code InnerClass} belongs to {@code Bar}, 57 | * which is in the correct package 58 | *
  • {@code com.foo.Baz -> sun.misc.Unsafe} will match because {@code Baz} is in the correct package 59 | *
  • {@code com.foo.Bar -> sun.misc.BASE64Decoder} will not match because {@code BASE64Decoder} is not {@code Unsafe} 60 | *
  • {@code com.foo.bar.Bar -> sun.misc.Unsafe} will not match because for a flat judge {@code com.foo.bar} does not 61 | * belong to {@code com.foo} 62 | *
63 | * 64 | *

Hierarchical Judge

65 | * A {@link PackageInclusion#HIERARCHICAL hierarchical} judge will interpret package names similar to folders and thus create a relation where packages can 66 | * contain other packages, e.g. {@code sun} contains {@code sun.misc}. 67 | * 68 | *

Example

69 | * Given the rule {@code (com.foo.Bar -> sun.misc.Unsafe; WARN)}: 70 | *
    71 | *
  • {@code com.foo.Bar -> sun.misc.Unsafe} will match 72 | *
  • {@code com.foo.Bar.InnerClass -> sun.misc.Unsafe} will match because {@code InnerClass} belongs to {@code Bar} 73 | *
  • {@code com.foo.Baz -> sun.misc.Unsafe} will not match because {@code Baz} is not {@code Bar} 74 | *
  • {@code com.foo.Bar -> sun.misc.BASE64Decoder} will not match because {@code BASE64Decoder} is not {@code Unsafe} 75 | *
76 | * Given the rule {@code (com.foo -> sun.misc.Unsafe; WARN)}: 77 | *
    78 | *
  • {@code com.foo.Bar -> sun.misc.Unsafe} will match because {@code Bar} is in the correct package 79 | *
  • {@code com.foo.Bar.InnerClass -> sun.misc.Unsafe} will match because {@code InnerClass} belongs to {@code Bar}, 80 | * which is in the correct package 81 | *
  • {@code com.foo.Baz -> sun.misc.Unsafe} will match because {@code Baz} is in the correct package 82 | *
  • {@code com.foo.Bar -> sun.misc.BASE64Decoder} will not match because {@code BASE64Decoder} is not {@code Unsafe} 83 | *
  • {@code com.foo.bar.Bar -> sun.misc.Unsafe} will match because for a hierarchical judge {@code com.foo.bar} 84 | * belongs to {@code com.foo} 85 | *
86 | */ 87 | public interface DependencyJudge { 88 | 89 | /** 90 | * Indicates the severity of the specified dependency {@code dependent -> dependency}. 91 | * 92 | * @param dependent 93 | * the type which depends on the the other type 94 | * @param dependency 95 | * the type upon which the {@code dependent} depends 96 | * 97 | * @return the severity of the dependency 98 | */ 99 | default Severity judgeSeverity(Type dependent, Type dependency) { 100 | return judgeSeverity(dependent.getFullyQualifiedName(), dependency.getFullyQualifiedName()); 101 | } 102 | 103 | /** 104 | * Indicates the severity of the specified dependency. 105 | * 106 | * @param dependentName 107 | * fully qualified name of the type or package which depends on the the other 108 | * @param dependencyName 109 | * fully qualified name of the type or package upon which the {@code dependent} depends 110 | * 111 | * @return the severity of the dependency 112 | */ 113 | Severity judgeSeverity(String dependentName, String dependencyName); 114 | 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/ArrowRuleParserTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.codehaus.plexus.classworlds.launcher.ConfigurationException; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | 9 | import static java.lang.String.format; 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 12 | 13 | /** 14 | * Tests {@link ArrowRuleParser}. 15 | */ 16 | public class ArrowRuleParserTest { 17 | 18 | @Rule 19 | public ExpectedException thrown = ExpectedException.none(); 20 | 21 | // #begin PARSE - EDGE CASES 22 | 23 | @Test(expected = NullPointerException.class) 24 | public void parseRules_rulesNull_throwsException() throws Exception { 25 | ArrowRuleParser.parseRules(null); 26 | } 27 | 28 | @Test 29 | public void parseRules_rulesEmpty_emptyList() throws Exception { 30 | ImmutableList rules = ArrowRuleParser.parseRules(""); 31 | assertThat(rules).isEmpty(); 32 | } 33 | 34 | // #end PARSE - EDGE CASES 35 | 36 | // #begin PARSE - INVALID CASES 37 | 38 | /* 39 | * In general see 'DependencyRuleTest' for the different ways in which rules can be invalid 40 | */ 41 | 42 | @Test 43 | public void parseRules_invalidRule_throwsException() throws Exception { 44 | thrown.expect(ConfigurationException.class); 45 | thrown.expectMessage("The line 'fooBar' defines no valid rule."); 46 | 47 | ArrowRuleParser.parseRules("fooBar"); 48 | } 49 | 50 | @Test 51 | public void parseRules_invalidArrows_throwsException() throws Exception { 52 | assertThatThrownBy( 53 | () -> ArrowRuleParser.parseRules("com.foo.Bar ~> sun.misc.Unsafe: WARN")) 54 | .isInstanceOf(ConfigurationException.class) 55 | .hasMessageContaining("The line 'com.foo.Bar ~> sun.misc.Unsafe: WARN' defines no valid rule."); 56 | 57 | assertThatThrownBy( 58 | () -> ArrowRuleParser.parseRules("com.foo.Bar to sun.misc.Unsafe: WARN")) 59 | .isInstanceOf(ConfigurationException.class) 60 | .hasMessageContaining("The line 'com.foo.Bar to sun.misc.Unsafe: WARN' defines no valid rule."); 61 | } 62 | 63 | @Test 64 | public void parseRules_invalidRuleInSecondLine_throwsException() throws Exception { 65 | thrown.expect(ConfigurationException.class); 66 | thrown.expectMessage("The line 'fooBar' defines no valid rule."); 67 | 68 | ArrowRuleParser.parseRules("com.foo.Bar -> sun.misc.Unsafe: WARN\n" 69 | + "fooBar"); 70 | } 71 | 72 | @Test 73 | public void parseRules_invalidRuleInOneOfManyLines_throwsException() throws Exception { 74 | thrown.expect(ConfigurationException.class); 75 | thrown.expectMessage("The line 'fooBar' defines no valid rule."); 76 | 77 | ArrowRuleParser.parseRules("com.foo.Bar -> sun.misc.Unsafe: WARN\n" 78 | + "fooBar\n" 79 | + "com.foo.Bar -> sun.misc: WARN\n" 80 | + "com.foo.Bar -> sun: WARN\n"); 81 | } 82 | 83 | @Test 84 | public void parseRules_twoRulesInOneLine_throwsException() throws Exception { 85 | String line = "com.foo.Bar -> sun.misc.Unsafe: WARN com.foo.Bar -> sun.misc.Unsafe: WARN"; 86 | 87 | thrown.expect(ConfigurationException.class); 88 | thrown.expectMessage(format("The line '%s' defines multiple rules.", line)); 89 | 90 | ArrowRuleParser.parseRules(line); 91 | } 92 | 93 | // #end PARSE - INVALID CASES 94 | 95 | // #begin PARSE - VALID CASES 96 | 97 | @Test 98 | public void parseRules_singleLineValidArrowRule_returnsRule() throws Exception { 99 | ImmutableList rules = ArrowRuleParser.parseRules("com.foo.Bar -> sun.misc.Unsafe: WARN"); 100 | 101 | assertThat(rules).hasSize(1); 102 | assertThat(rules.get(0)).isEqualTo(DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.WARN)); 103 | } 104 | 105 | @Test 106 | public void parseRules_singleLineValidArrowRuleWithSpaces_returnsRule() throws Exception { 107 | ImmutableList rules = ArrowRuleParser.parseRules(" com.foo.Bar -> sun.misc.Unsafe : WARN "); 108 | 109 | assertThat(rules).hasSize(1); 110 | assertThat(rules.get(0)).isEqualTo(DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.WARN)); 111 | } 112 | 113 | @Test 114 | public void parseRules_multipleLinesValidArrowRule_returnsRules() throws Exception { 115 | // note the empty lines and the various kinds of line breaks 116 | ImmutableList rules = ArrowRuleParser.parseRules("\n" 117 | + "com.foo.Bar -> sun.misc.Unsafe: INFORM\r\n" 118 | + "\n\r\n\r" 119 | + "com.foo.Bar -> sun.misc.Unsafe: WARN\r" 120 | + "com.foo.Bar -> sun.misc.Unsafe: FAIL" 121 | ); 122 | 123 | assertThat(rules).hasSize(3); 124 | assertThat(rules.get(0)).isEqualTo(DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.INFORM)); 125 | assertThat(rules.get(1)).isEqualTo(DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.WARN)); 126 | assertThat(rules.get(2)).isEqualTo(DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL)); 127 | } 128 | 129 | @Test 130 | public void parseRules_singleLineValidOnRule_returnsRule() throws Exception { 131 | ImmutableList rules = ArrowRuleParser.parseRules("com.foo.Bar on sun.misc.Unsafe: WARN"); 132 | 133 | assertThat(rules).hasSize(1); 134 | assertThat(rules.get(0)).isEqualTo(DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.WARN)); 135 | } 136 | 137 | // #end PARSE - VALID CASES 138 | 139 | // #begin TO STRING 140 | 141 | @Test(expected = NullPointerException.class) 142 | public void ruleToArrowString_arrowNull_throwsException() { 143 | DependencyRule rule = DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL); 144 | ArrowRuleParser.ruleToArrowString(null, rule); 145 | } 146 | 147 | @Test(expected = NullPointerException.class) 148 | public void ruleToArrowString_ruleNull_throwsException() { 149 | ArrowRuleParser.ruleToArrowString(Arrow.ARROW, null); 150 | } 151 | 152 | @Test 153 | public void ruleToArrowString_validArrow_arrowUsed() throws Exception { 154 | DependencyRule rule = DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL); 155 | 156 | // " -> " 157 | String line = ArrowRuleParser.ruleToArrowString(Arrow.ARROW, rule); 158 | assertThat(line).contains(" " + Arrow.ARROW.text() + " "); 159 | 160 | // " on " 161 | line = ArrowRuleParser.ruleToArrowString(Arrow.ON, rule); 162 | assertThat(line).contains(" " + Arrow.ON.text() + " "); 163 | } 164 | 165 | @Test 166 | public void ruleToArrowString_validRule_resultingStringIsParsable() throws Exception { 167 | DependencyRule ruleIn = DependencyRule.of("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL); 168 | 169 | String line = ArrowRuleParser.ruleToArrowString(Arrow.ARROW, ruleIn); 170 | ImmutableList rulesOut = ArrowRuleParser.parseRules(line); 171 | 172 | assertThat(rulesOut).hasSize(1); 173 | assertThat(rulesOut.get(0)).isEqualTo(ruleIn); 174 | } 175 | 176 | // #end TO STRING 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/parse/ViolationParser.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.parse; 2 | 3 | import org.codefx.mvn.jdeps.dependency.InternalType; 4 | import org.codefx.mvn.jdeps.dependency.Type; 5 | import org.codefx.mvn.jdeps.dependency.Violation; 6 | import org.codefx.mvn.jdeps.dependency.Violation.ViolationBuilder; 7 | import org.codefx.mvn.jdeps.mojo.MojoLogging; 8 | 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | import java.util.function.Consumer; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | import static java.lang.String.format; 16 | 17 | /** 18 | * Parses violation blocks from the JDeps output line by line and hands created {@link Violation}s to a 19 | * {@link Consumer} that can further process it. 20 | */ 21 | public class ViolationParser { 22 | 23 | public static final String MESSAGE_MARKER_JDEPS_LINE = "[d]"; 24 | public static final String MESSAGE_MARKER_UNKNOWN_LINE = "[ ]"; 25 | private static final String MESSAGE_PARSED_LINE = " %s %s"; 26 | 27 | /** 28 | * Pattern to match the reported type line, e.g. 29 | * 30 | *
 31 | 	 * 	   org.codefx.lab.App (...)
 32 | 	 * 
33 | */ 34 | private static final Pattern REPORTED_TYPE_PATTERN = Pattern.compile("" 35 | + "\\s+" // leading spaces 36 | + "([a-zA-Z_][\\.\\w]*)" // qualified class name (simplified), e.g. "sun.misc.Unsafe" 37 | + "\\s+" // spaces to separate class name 38 | + ".*"); 39 | 40 | private final InternalTypeLineParser internalTypeLineParser; 41 | private final Consumer violationConsumer; 42 | private LineParserState lineParser; 43 | 44 | /** 45 | * Creates a new parser. 46 | * 47 | * @param violationConsumer 48 | * the {@link Consumer} to which parsed {@link Violation}s are handed over 49 | */ 50 | public ViolationParser(Consumer violationConsumer) { 51 | this(new InternalTypeLineParser(), violationConsumer); 52 | } 53 | 54 | /** 55 | * Creates a new parser. 56 | * 57 | * @param internalTypeLineParser 58 | * used to parse individual internal dependencies 59 | * @param violationConsumer 60 | * the {@link Consumer} to which parsed {@link Violation}s are handed over 61 | */ 62 | public ViolationParser(InternalTypeLineParser internalTypeLineParser, Consumer violationConsumer) { 63 | Objects.requireNonNull(internalTypeLineParser, "The argument 'internalTypeLineParser' must not be null."); 64 | Objects.requireNonNull(violationConsumer, "The argument 'violationConsumer' must not be null."); 65 | 66 | this.internalTypeLineParser = internalTypeLineParser; 67 | this.violationConsumer = violationConsumer; 68 | this.lineParser = new NoBlock(); 69 | } 70 | 71 | // #begin PARSE SUPPORT 72 | 73 | /** 74 | * Parses the specified line. 75 | *

76 | * As soon as a new {@link Violation} is created it is handed to the {@link Consumer} specified during 77 | * construction. 78 | * 79 | * @param line 80 | * the line to parse 81 | */ 82 | public void parseLine(String line) { 83 | Objects.requireNonNull(line, "The argument 'line' must not be null."); 84 | lineParser = lineParser.parseLine(line); 85 | lineParser.logLine(line); 86 | } 87 | 88 | /** 89 | * Informs the parser that parsing is done (for now). 90 | *

91 | * If a violation is currently being created, calling this method will build it. 92 | */ 93 | public void finish() { 94 | lineParser = lineParser.parseLine(""); 95 | } 96 | 97 | private LineParserState determineWhetherNewBlockStarted(String line) { 98 | Optional asFirstBlockLine = parseAsFirstBlockLine(line); 99 | if (asFirstBlockLine.isPresent()) 100 | return new BlockBegan(asFirstBlockLine.get()); 101 | else 102 | return new NoBlock(); 103 | } 104 | 105 | private static Optional parseAsFirstBlockLine(String line) { 106 | Matcher firstLineMatcher = REPORTED_TYPE_PATTERN.matcher(line); 107 | boolean isFirstLine = firstLineMatcher.matches(); 108 | if (isFirstLine) 109 | return Optional.of(firstLineMatcher.group(1)); 110 | else 111 | return Optional.empty(); 112 | } 113 | 114 | // #end PARSE SUPPORT 115 | 116 | // #begin PARSE STATE MACHINE 117 | 118 | private interface LineParserState { 119 | 120 | LineParserState parseLine(String line); 121 | 122 | void logLine(String line); 123 | } 124 | 125 | /** 126 | * There is currently no violations block. 127 | *

128 | * The next line may start a new block if it is a {@link #REPORTED_TYPE_PATTERN REPORTED_TYPE}. 129 | */ 130 | private class NoBlock implements LineParserState { 131 | 132 | @Override 133 | public LineParserState parseLine(String line) { 134 | return determineWhetherNewBlockStarted(line); 135 | } 136 | 137 | @Override 138 | public void logLine(String line) { 139 | MojoLogging.logger().debug(format(MESSAGE_PARSED_LINE, MESSAGE_MARKER_UNKNOWN_LINE, line)); 140 | } 141 | 142 | } 143 | 144 | /** 145 | * A block began and a violation is being build. 146 | *

147 | * The block can either be continued with an internal dependency or may end. If it ends: 148 | *

    149 | *
  • the violation which is currently being build is finished and handed to the {@link #violationConsumer} 150 | *
  • a new block might start with the next line ~> transition to new {@link BlockBegan} 151 | *
  • some other lines might occur ~> transition to {@link NoBlock} 152 | *
153 | */ 154 | private class BlockBegan implements LineParserState { 155 | 156 | private final ViolationBuilder violationBuilder; 157 | 158 | public BlockBegan(String fullyQualifiedClassName) { 159 | assert fullyQualifiedClassName != null : "The argument 'fullyQualifiedClassName' must not be null."; 160 | 161 | Type dependent = Type.of(fullyQualifiedClassName); 162 | violationBuilder = Violation.buildForDependent(dependent); 163 | } 164 | 165 | @Override 166 | public LineParserState parseLine(String line) { 167 | assert line != null : "The argument 'line' must not be null."; 168 | 169 | boolean lineCouldBeProcessed = processLine(line); 170 | return computeNextState(line, lineCouldBeProcessed); 171 | } 172 | 173 | private boolean processLine(String line) { 174 | Optional parsedInternalType = internalTypeLineParser.parseLine(line); 175 | parsedInternalType.ifPresent(violationBuilder::addDependency); 176 | return parsedInternalType.isPresent(); 177 | } 178 | 179 | private LineParserState computeNextState(String line, boolean lineCouldBeProcessed) { 180 | if (lineCouldBeProcessed) 181 | return this; 182 | else { 183 | finishViolation(); 184 | return determineWhetherNewBlockStarted(line); 185 | } 186 | } 187 | 188 | private void finishViolation() { 189 | Violation violation = violationBuilder.build(); 190 | violationConsumer.accept(violation); 191 | } 192 | 193 | @Override 194 | public void logLine(String line) { 195 | MojoLogging.logger().debug(format(MESSAGE_PARSED_LINE, MESSAGE_MARKER_JDEPS_LINE, line)); 196 | } 197 | 198 | } 199 | 200 | // #end PARSE STATE MACHINE 201 | } 202 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | 6 | 7 | org.codefx.mvn 8 | jdeps-maven-plugin 9 | 0.2 10 | maven-plugin 11 | 12 | 13 | 14 | JDeps Maven Plugin 15 | Includes JDeps in Maven 16 | https://github.com/CodeFX-org/JDeps-Maven-Plugin 17 | 18 | 19 | https://github.com/CodeFX-org/JDeps-Maven-Plugin 20 | scm:git:git://github.com/CodeFX-org/JDeps-Maven-Plugin.git 21 | 22 | 23 | 24 | https://github.com/CodeFX-org/JDeps-Maven-Plugin/issues 25 | GitHub 26 | 27 | 28 | 29 | 30 | ossrh 31 | https://oss.sonatype.org/content/repositories/snapshots 32 | 33 | 34 | 35 | 36 | 37 | GNU General Public License, Version 3.0 38 | http://www.gnu.org/licenses/gpl-3.0.txt 39 | repo 40 | Dual licensing under a license without copyleft effect is possible. Contact nipa@codefx.org. 41 | 42 | 43 | 44 | 45 | 46 | 47 | CodeFX 48 | http://codefx.org 49 | 50 | 51 | 52 | 53 | nipa 54 | Nicolai Parlog 55 | nipa@codefx.org 56 | CodeFX 57 | http://codefx.org 58 | +1 59 | 60 | 61 | 62 | 63 | 64 | 65 | UTF-8 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven 72 | maven-plugin-api 73 | 3.3.3 74 | 75 | 76 | org.apache.maven.plugin-tools 77 | maven-plugin-annotations 78 | 3.4 79 | provided 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-toolchains-plugin 85 | 1.1 86 | 87 | 88 | 89 | com.google.guava 90 | guava 91 | 18.0 92 | 93 | 94 | org.apache.commons 95 | commons-lang3 96 | 3.4 97 | 98 | 99 | 100 | 101 | junit 102 | junit 103 | 4.12 104 | test 105 | 106 | 107 | 108 | org.mockito 109 | mockito-all 110 | 1.10.19 111 | test 112 | 113 | 114 | 115 | org.assertj 116 | assertj-core 117 | 3.2.0 118 | test 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-compiler-plugin 128 | 3.3 129 | 130 | 1.8 131 | 1.8 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-plugin-plugin 138 | 3.3 139 | 140 | 141 | 142 | org.apache.maven.plugins 143 | maven-toolchains-plugin 144 | 1.1 145 | 146 | 147 | validate 148 | 149 | toolchain 150 | 151 | 152 | 153 | 154 | 155 | 156 | 1.8 157 | 158 | 159 | 160 | 161 | 162 | 163 | org.apache.maven.plugins 164 | maven-source-plugin 165 | 2.4 166 | 167 | 168 | attach-sources 169 | 170 | jar 171 | 172 | 173 | 174 | 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-javadoc-plugin 179 | 2.10.3 180 | 181 | 182 | attach-javadocs 183 | 184 | jar 185 | 186 | 187 | 188 | 189 | 190 | 191 | CC-BY 4.0
, 194 | attributed to Nicolai Parlog from 195 | CodeFX. 196 | ]]> 197 | 198 | 199 | 200 | 201 | 202 | org.apache.maven.plugins 203 | maven-gpg-plugin 204 | 1.6 205 | 206 | 207 | sign-artifacts 208 | verify 209 | 210 | sign 211 | 212 | 213 | 214 | 215 | 216 | 217 | org.sonatype.plugins 218 | nexus-staging-maven-plugin 219 | 1.6.5 220 | true 221 | 222 | ossrh 223 | https://oss.sonatype.org/ 224 | true 225 | 226 | 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /src/main/java/org/codefx/mvn/jdeps/dependency/Violation.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.dependency; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.Ordering; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.Collections; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.stream.Stream; 12 | 13 | import static java.lang.Integer.min; 14 | import static java.util.Objects.requireNonNull; 15 | import static java.util.stream.Collectors.joining; 16 | import static java.util.stream.Stream.concat; 17 | 18 | /** 19 | * A violation is a dependency of a class on another class which is marked as JDK-internal API by jdeps. 20 | *

21 | * It consists of a {@link Type} which depends on one or more {@link InternalType}s. 22 | */ 23 | public final class Violation implements Comparable { 24 | 25 | private final Type dependent; 26 | private final ImmutableList sortedInternalDependencies; 27 | 28 | /** 29 | * @throws IllegalStateException 30 | * if the list of internal dependencies is empty 31 | */ 32 | private Violation(Type dependent, Collection internalDependencies) { 33 | this.dependent = requireNonNull(dependent, "The argument 'dependent' must not be null."); 34 | 35 | requireNonNull(internalDependencies, "The argument 'internalDependencies' must not be null."); 36 | if (internalDependencies.size() == 0) 37 | throw new IllegalArgumentException( 38 | "A violation must contain at least one internal dependency."); 39 | sortedInternalDependencies = sorted(internalDependencies); 40 | } 41 | 42 | private static ImmutableList sorted(Iterable dependencies) { 43 | boolean dependenciesAreOrdered = Ordering.natural().isOrdered(dependencies); 44 | if (dependenciesAreOrdered) 45 | if (dependencies instanceof ImmutableList) 46 | return (ImmutableList) dependencies; 47 | else 48 | return ImmutableList.copyOf(dependencies); 49 | else 50 | return Ordering.natural().immutableSortedCopy(dependencies); 51 | } 52 | 53 | /** 54 | * Builds a new violation. 55 | * 56 | * @param dependent 57 | * the dependent which contains the violating dependency 58 | * @param internalDependencies 59 | * the types the dependent depends upon 60 | * 61 | * @return a {@link ViolationBuilder} 62 | * 63 | * @throws IllegalStateException 64 | * if the list of internal dependencies is empty 65 | */ 66 | public static Violation buildFor(Type dependent, Collection internalDependencies) { 67 | return new Violation(dependent, internalDependencies); 68 | } 69 | 70 | /** 71 | * Starts building a new violation. 72 | * 73 | * @param dependent 74 | * the dependent which contains the violating dependency 75 | * 76 | * @return a {@link ViolationBuilder} 77 | */ 78 | public static ViolationBuilder buildForDependent(Type dependent) { 79 | return new ViolationBuilder(dependent); 80 | } 81 | 82 | /** 83 | * @return the dependent which contains the dependencies on internal types 84 | */ 85 | public Type getDependent() { 86 | return dependent; 87 | } 88 | 89 | /** 90 | * @return the internal types upon which {@link #getDependent()} depends 91 | */ 92 | public ImmutableList getInternalDependencies() { 93 | return sortedInternalDependencies; 94 | } 95 | 96 | // #begin COMPARETO, EQUALS, HASHCODE, TOSTRING 97 | 98 | @Override 99 | public int compareTo(Violation other) { 100 | if (this == other) 101 | return 0; 102 | 103 | int comparisonByDependents = dependent.compareTo(other.dependent); 104 | if (comparisonByDependents != 0) 105 | return comparisonByDependents; 106 | 107 | // check the elements in both lists 108 | int maxIndex = min(sortedInternalDependencies.size(), other.sortedInternalDependencies.size()); 109 | for (int i = 0; i < maxIndex; i++) { 110 | int comparisonByDependencies = 111 | sortedInternalDependencies.get(i).compareTo(other.sortedInternalDependencies.get(i)); 112 | if (comparisonByDependencies != 0) 113 | return comparisonByDependencies; 114 | } 115 | // both lists contain the same elements up to the length of the shorter one; make the shorter one smaller 116 | return sortedInternalDependencies.size() - other.sortedInternalDependencies.size(); 117 | } 118 | 119 | @Override 120 | public boolean equals(Object obj) { 121 | if (this == obj) 122 | return true; 123 | if (obj == null) 124 | return false; 125 | if (getClass() != obj.getClass()) 126 | return false; 127 | 128 | Violation other = (Violation) obj; 129 | return Objects.equals(dependent, other.dependent) 130 | && Objects.equals(sortedInternalDependencies, other.sortedInternalDependencies); 131 | } 132 | 133 | @Override 134 | public int hashCode() { 135 | return Objects.hash(dependent, sortedInternalDependencies); 136 | } 137 | 138 | @Override 139 | public String toString() { 140 | String dependencies = sortedInternalDependencies 141 | .stream() 142 | .map(Object::toString) 143 | .collect(joining(", ", "{", "}")); 144 | return dependent + " -> " + dependencies; 145 | } 146 | 147 | /** 148 | * @return a representation of this violation that spans multiple lines 149 | */ 150 | public Stream toLines() { 151 | return toLines(" ", " -> "); 152 | } 153 | 154 | /** 155 | * @param allPrefix 156 | * the prefix before all lines; maybe an indent 157 | * @param dependencyPrefix 158 | * the prefix before the lines listing the dependencies; maybe an additional indent and an arrow 159 | * 160 | * @return a representation of this violation that spans multiple lines 161 | */ 162 | public Stream toLines(String allPrefix, String dependencyPrefix) { 163 | return concat( 164 | Stream.of(allPrefix + dependent), 165 | sortedInternalDependencies.stream() 166 | .map(InternalType::toString) 167 | .map(dependency -> allPrefix + dependencyPrefix + dependency) 168 | ); 169 | } 170 | 171 | // #end COMPARETO, EQUALS, HASHCODE, TOSTRING 172 | 173 | // #begin BUILDER 174 | 175 | /** 176 | * Allows to build a {@link Violation} (which is immutable) by successively adding dependecies. 177 | */ 178 | public static class ViolationBuilder { 179 | 180 | private final Type dependent; 181 | 182 | private final List internalDependencies; 183 | 184 | private ViolationBuilder(Type dependent) { 185 | this.dependent = requireNonNull(dependent, "The argument 'dependent' must not be null."); 186 | this.internalDependencies = new ArrayList<>(); 187 | } 188 | 189 | /** 190 | * Adds the specified {@link InternalType} as a dependency. 191 | * 192 | * @param dependency 193 | * an internal dependent 194 | * 195 | * @return this builder 196 | */ 197 | public ViolationBuilder addDependency(InternalType dependency) { 198 | requireNonNull(dependency, "The argument 'dependency' must not be null."); 199 | 200 | internalDependencies.add(dependency); 201 | return this; 202 | } 203 | 204 | /** 205 | * Adds the specified {@link InternalType}s as dependencies. 206 | * 207 | * @param dependencies 208 | * a variable number of internal types 209 | * 210 | * @return this builder 211 | */ 212 | public ViolationBuilder addDependencies(InternalType... dependencies) { 213 | requireNonNull(dependencies, "The argument 'dependencies' must not be null."); 214 | 215 | Collections.addAll(internalDependencies, dependencies); 216 | return this; 217 | } 218 | 219 | /** 220 | * Adds the specified {@link InternalType}s as dependencies. 221 | * 222 | * @param dependencies 223 | * an iterable of internal types 224 | * 225 | * @return this builder 226 | */ 227 | public ViolationBuilder addDependencies(Iterable dependencies) { 228 | requireNonNull(dependencies, "The argument 'dependencies' must not be null."); 229 | 230 | dependencies.forEach(internalDependencies::add); 231 | return this; 232 | } 233 | 234 | /** 235 | * @return a new {@link Violation} 236 | * 237 | * @throws IllegalStateException 238 | * if the list of internal dependencies is empty 239 | */ 240 | public Violation build() { 241 | try { 242 | return new Violation(dependent, internalDependencies); 243 | } catch (IllegalArgumentException ex) { 244 | String message = "The violation could not be built because it contains no internal dependencies. " + 245 | "Maybe the violation block ended prematurely?"; 246 | throw new IllegalStateException(message, ex); 247 | } 248 | } 249 | 250 | } 251 | 252 | // #end BUILDER 253 | 254 | } 255 | -------------------------------------------------------------------------------- /src/test/java/org/codefx/mvn/jdeps/rules/AbstractDependencyJudgeTest.java: -------------------------------------------------------------------------------- 1 | package org.codefx.mvn.jdeps.rules; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | /** 8 | * Abstract superclass for tests of {@link DependencyJudge} implementations. 9 | */ 10 | public abstract class AbstractDependencyJudgeTest { 11 | 12 | // #begin BUILDER 13 | 14 | @Test(expected = NullPointerException.class) 15 | public void addDependency_ruleNull_throwsException() { 16 | builder().addDependency(null); 17 | } 18 | 19 | @Test(expected = IllegalArgumentException.class) 20 | public void addDependency_alreadyDefinedWithDifferentSeverity_throwsException() { 21 | builder(). 22 | withDefaultSeverity(Severity.FAIL) 23 | .addDependency(DependencyRule.of("com.foo.bar", "sun.misc.Unsafe", Severity.INFORM)) 24 | .addDependency(DependencyRule.of("com.foo.bar", "sun.misc.Unsafe", Severity.WARN)); 25 | } 26 | 27 | @Test 28 | public void addDependency_alreadyDefinedWithSameSeverity_throwsNoException() { 29 | builder(). 30 | withDefaultSeverity(Severity.FAIL) 31 | .addDependency(DependencyRule.of("com.foo.bar", "sun.misc.Unsafe", Severity.WARN)) 32 | .addDependency(DependencyRule.of("com.foo.bar", "sun.misc.Unsafe", Severity.WARN)); 33 | } 34 | 35 | @Test(expected = IllegalStateException.class) 36 | public void build_alreadyCalled_throwsException() { 37 | DependencyJudgeBuilder builder = builder().withDefaultSeverity(Severity.FAIL); 38 | builder.build(); 39 | builder.build(); 40 | } 41 | 42 | // #end BUILDER 43 | 44 | // #begin JUDGE 45 | 46 | @Test 47 | public void judgeSeverity_noRules_defaultValue() { 48 | for (Severity defaultSeverity : Severity.values()) { 49 | DependencyJudge judge = builder(). 50 | withDefaultSeverity(defaultSeverity).build(); 51 | // class -> class 52 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe")).isSameAs(defaultSeverity); 53 | // class -> package 54 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc")).isSameAs(defaultSeverity); 55 | assertThat(judge.judgeSeverity("com.foo.Baz", "sun")).isSameAs(defaultSeverity); 56 | // package -> class 57 | assertThat(judge.judgeSeverity("com.foo", "sun.misc.Unsafe")).isSameAs(defaultSeverity); 58 | assertThat(judge.judgeSeverity("com", "sun.misc.Unsafe")).isSameAs(defaultSeverity); 59 | // package -> package 60 | assertThat(judge.judgeSeverity("com.foo", "sun.misc")).isSameAs(defaultSeverity); 61 | assertThat(judge.judgeSeverity("com.foo", "sun.misc")).isSameAs(defaultSeverity); 62 | } 63 | } 64 | 65 | @Test 66 | public void judgeSeverity_exactMatch_definedSeverity() { 67 | DependencyJudge judge = builder(). 68 | withDefaultSeverity(Severity.INFORM) 69 | // this rule should be applied 70 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 71 | .build(); 72 | 73 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 74 | assertThat(severity).isSameAs(Severity.FAIL); 75 | } 76 | 77 | @Test 78 | public void judgeSeverity_dependentOccursInMoreSpecialRule_ruleIsNotApplied() { 79 | DependencyJudge judge = builder(). 80 | withDefaultSeverity(Severity.INFORM) 81 | // the rule defined for "com.foo.Bar" MUST NOT be applied to "com.foo" 82 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 83 | .build(); 84 | 85 | Severity severity = judge.judgeSeverity("com.foo", "sun.misc.Unsafe"); 86 | assertThat(severity).isSameAs(Severity.INFORM); 87 | } 88 | 89 | @Test 90 | public void judgeSeverity_dependentOccursInSamePackageRule_ruleIsNotApplied() { 91 | DependencyJudge judge = builder(). 92 | withDefaultSeverity(Severity.INFORM) 93 | // the rule defined for "com.foo.Bar" MUST NOT be applied to "com.foo.Baz" 94 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 95 | .build(); 96 | 97 | Severity severity = judge.judgeSeverity("com.foo.Baz", "sun.misc.Unsafe"); 98 | assertThat(severity).isSameAs(Severity.INFORM); 99 | } 100 | 101 | @Test 102 | public void judgeSeverity_dependentCoveredByMoreGeneralRuleForContainingPackage_ruleIsApplied() { 103 | DependencyJudge judge = builder(). 104 | withDefaultSeverity(Severity.INFORM) 105 | // this rule should be applied 106 | .addDependency("com.foo", "sun.misc.Unsafe", Severity.FAIL) 107 | .build(); 108 | 109 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 110 | assertThat(severity).isSameAs(Severity.FAIL); 111 | } 112 | 113 | @Test 114 | public void judgeSeverity_dependencyOccursInMoreSpecialRule_ruleIsNotApplied() { 115 | DependencyJudge judge = builder(). 116 | withDefaultSeverity(Severity.INFORM) 117 | // this rule defined for "sun.misc.Unsafe" MUST NOT be applied to "sun.misc" 118 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 119 | .build(); 120 | 121 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc"); 122 | assertThat(severity).isSameAs(Severity.INFORM); 123 | } 124 | 125 | @Test 126 | public void judgeSeverity_dependencyOccursInSamePackageRule_ruleIsNotApplied() { 127 | DependencyJudge judge = builder(). 128 | withDefaultSeverity(Severity.INFORM) 129 | // this rule defined for "sun.misc.Unsafe" MUST NOT be applied to "sun.misc.BASE64Encoder" 130 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 131 | .build(); 132 | 133 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.BASE64Encoder"); 134 | assertThat(severity).isSameAs(Severity.INFORM); 135 | } 136 | 137 | @Test 138 | public void judgeSeverity_dependencyCoveredByMoreGeneralRuleForContainingPackage_ruleIsApplied() { 139 | DependencyJudge judge = builder(). 140 | withDefaultSeverity(Severity.INFORM) 141 | // this rule should be applied 142 | .addDependency("com.foo.Bar", "sun.misc", Severity.FAIL) 143 | .build(); 144 | 145 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 146 | assertThat(severity).isSameAs(Severity.FAIL); 147 | } 148 | 149 | @Test 150 | public void judgeSeverity_bothCoveredByMoreGeneralRule_ruleIsApplied() { 151 | DependencyJudge judge = builder(). 152 | withDefaultSeverity(Severity.INFORM) 153 | // this rule should be applied 154 | .addDependency("com.foo", "sun.misc", Severity.FAIL) 155 | .build(); 156 | 157 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 158 | assertThat(severity).isSameAs(Severity.FAIL); 159 | } 160 | 161 | @Test 162 | public void judgeSeverity_dependentCoveredByTwoRules_mostSpecialRuleIsApplied() { 163 | DependencyJudge judge = builder(). 164 | withDefaultSeverity(Severity.INFORM) 165 | .addDependency("com.foo", "sun.misc", Severity.WARN) 166 | .addDependency("com.foo.Bar", "sun.misc", Severity.FAIL) 167 | .addDependency("com.foo.Bar.Inner", "sun.misc", Severity.WARN) 168 | .build(); 169 | 170 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 171 | assertThat(severity).isSameAs(Severity.FAIL); 172 | } 173 | 174 | @Test 175 | public void judgeSeverity_dependencyCoveredByTwoRules_mostSpecialRuleIsApplied() { 176 | DependencyJudge judge = builder(). 177 | withDefaultSeverity(Severity.INFORM) 178 | .addDependency("com.foo.Bar", "sun.misc", Severity.WARN) 179 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 180 | .addDependency("com.foo.Bar", "sun.misc.Unsafe.Inner", Severity.WARN) 181 | .build(); 182 | 183 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 184 | assertThat(severity).isSameAs(Severity.FAIL); 185 | } 186 | 187 | @Test 188 | public void judgeSeverity_bothCoveredByTwoRules_mostSpecialRuleIsApplied() { 189 | DependencyJudge judge = builder(). 190 | withDefaultSeverity(Severity.INFORM) 191 | .addDependency("com.foo", "sun.misc.Unsafe", Severity.WARN) 192 | .addDependency("com.foo.Bar", "sun.misc", Severity.WARN) 193 | .addDependency("com.foo.Bar", "sun.misc.Unsafe", Severity.FAIL) 194 | .addDependency("com.foo.Bar.Inner", "sun.misc.Unsafe", Severity.WARN) 195 | .addDependency("com.foo.Bar", "sun.misc.Unsafe.Inner", Severity.WARN) 196 | .build(); 197 | 198 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 199 | assertThat(severity).isSameAs(Severity.FAIL); 200 | } 201 | 202 | @Test 203 | public void judgeSeverity_bestMatchingDependentHasNoRuleForDependency_moreGeneralRuleIsApplied() { 204 | DependencyJudge judge = builder(). 205 | withDefaultSeverity(Severity.INFORM) 206 | // this rule should be applied 207 | .addDependency("com.foo", "sun.misc", Severity.FAIL) 208 | // this rule matches the dependant but has no rule for the dependency, so it should not be applied 209 | .addDependency("com.foo.Bar", "sun.misc.Unsafe.Inner", Severity.WARN) 210 | .build(); 211 | 212 | Severity severity = judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe"); 213 | assertThat(severity).isSameAs(Severity.FAIL); 214 | 215 | // for demonstration purposes, check that the second rule does apply given a matching dependency 216 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe.Inner")).isSameAs(Severity.WARN); 217 | } 218 | 219 | @Test 220 | public void judgeSeverity_dependentCoveredByWildcardRule_ruleIsApplied() { 221 | DependencyJudge judge = builder(). 222 | withDefaultSeverity(Severity.INFORM) 223 | .addDependency(DependencyRule.ALL_TYPES_WILDCARD, "sun.misc.Unsafe", Severity.FAIL) 224 | .build(); 225 | 226 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe")).isSameAs(Severity.FAIL); 227 | assertThat(judge.judgeSeverity("net.Foo", "sun.misc.Unsafe")).isSameAs(Severity.FAIL); 228 | assertThat(judge.judgeSeverity("org", "sun.misc.Unsafe")).isSameAs(Severity.FAIL); 229 | } 230 | 231 | @Test 232 | public void judgeSeverity_dependencyCoveredByWildcardRule_ruleIsApplied() { 233 | DependencyJudge judge = builder(). 234 | withDefaultSeverity(Severity.INFORM) 235 | .addDependency("com.foo.Bar", DependencyRule.ALL_TYPES_WILDCARD, Severity.FAIL) 236 | .build(); 237 | 238 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe")).isSameAs(Severity.FAIL); 239 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc.BASE64Encoder")).isSameAs(Severity.FAIL); 240 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.")).isSameAs(Severity.FAIL); 241 | } 242 | 243 | @Test 244 | public void judgeSeverity_bothDependenciesCoveredByWildcardRule_ruleIsApplied() { 245 | DependencyJudge judge = builder(). 246 | withDefaultSeverity(Severity.INFORM) 247 | .addDependency(DependencyRule.ALL_TYPES_WILDCARD, DependencyRule.ALL_TYPES_WILDCARD, Severity.FAIL) 248 | .build(); 249 | 250 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.misc.Unsafe")).isSameAs(Severity.FAIL); 251 | assertThat(judge.judgeSeverity("com.foo.Bar", "sun.")).isSameAs(Severity.FAIL); 252 | assertThat(judge.judgeSeverity("com", "sun.misc.BASE64Encoder")).isSameAs(Severity.FAIL); 253 | } 254 | 255 | // #end JUDGE 256 | 257 | /** 258 | * @return the builder for the {@code DependencyJudgeBuilder} tested by this class 259 | */ 260 | protected abstract DependencyJudgeBuilder builder(); 261 | 262 | } 263 | --------------------------------------------------------------------------------