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 | *