package org.codefx.mvn.jdeps.rules; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import java.util.HashMap; import java.util.Map; import java.util.Optional; import static java.text.MessageFormat.format; import static java.util.Objects.requireNonNull; /** * A {@link DependencyJudge} based on a bimap {@code (dependency, dependant) -> severity} and using * {@link TypeNameHierarchy}-s to identify the best match. */ public class MapDependencyJudge implements DependencyJudge { private final PackageInclusion packageInclusion; private final Severity defaultSeverity; private final Map<String, Map<String, Severity>> dependencies; private MapDependencyJudge( PackageInclusion packageInclusion, Severity defaultSeverity, Map<String, Map<String, Severity>> dependencies) { this.packageInclusion = requireNonNull(packageInclusion, "The argument 'packageInclusion' must not be null."); this.defaultSeverity = requireNonNull(defaultSeverity, "The argument 'defaultSeverity' must not be null."); this.dependencies = requireNonNull(dependencies, "The argument 'dependencies' must not be null."); } @Override public Severity judgeSeverity(String dependentName, String dependencyName) { // the order of the two loops is crucial; // checking all dependency names before continuing with the next dependent name yields the desired behavior of // finding the best matching dependent that defines a rule for the dependency for (String dependentNamePart : namesFor(dependentName)) for (String dependencyNamePart : namesFor(dependencyName)) { Optional<Severity> severity = tryGetSeverityFor(dependentNamePart, dependencyNamePart); if (severity.isPresent()) return severity.get(); } return defaultSeverity; } private Optional<Severity> tryGetSeverityFor(String dependent, String dependency) { return Optional.ofNullable(dependencies.get(dependent)) .map(mapForDependent -> mapForDependent.get(dependency)); } private Iterable<String> namesFor(String dependentName) { Iterable<String> typeNameHierarchy = TypeNameHierarchy.forFullyQualifiedName(dependentName, packageInclusion); Iterable<String> wildcard = () -> Iterators.singletonIterator(DependencyRule.ALL_TYPES_WILDCARD); return Iterables.concat(typeNameHierarchy, wildcard); } public static class MapDependencyJudgeBuilder implements DependencyJudgeBuilder { private PackageInclusion packageInclusion; private Severity defaultSeverity; private final Map<String, Map<String, Severity>> dependencies; private boolean alreadyBuilt; public MapDependencyJudgeBuilder() { // set default values packageInclusion = PackageInclusion.FLAT; defaultSeverity = Severity.FAIL; dependencies = new HashMap<>(); alreadyBuilt = false; } @Override public DependencyJudgeBuilder withInclusion(PackageInclusion packageInclusion) { this.packageInclusion = requireNonNull(packageInclusion, "The argument 'packageInclusion' must not be null."); return this; } @Override public DependencyJudgeBuilder withDefaultSeverity(Severity defaultSeverity) { this.defaultSeverity = requireNonNull(defaultSeverity, "The argument 'defaultSeverity' must not be null."); return this; } @Override public DependencyJudgeBuilder addDependency(DependencyRule rule) { requireNonNull(rule, "The argument 'ruleName' must not be null."); Map<String, Severity> mapForDependent = dependencies.computeIfAbsent(rule.getDependent(), ignored -> new HashMap<>()); Severity previousSeverity = mapForDependent.put(rule.getDependency(), rule.getSeverity()); if (previousSeverity != null && previousSeverity != rule.getSeverity()) { String message = format( "The dependency '{0} -> {1}' is defined with multiple severitues {2} and {3}.", rule.getDependent(), rule.getDependency(), previousSeverity, rule.getSeverity()); throw new IllegalArgumentException(message); } return this; } public DependencyJudge build() { if (alreadyBuilt) throw new IllegalStateException("A builder can only be used once."); alreadyBuilt = true; return new MapDependencyJudge(packageInclusion, defaultSeverity, dependencies); } } }