package org.codefx.mvn.jdeps.rules;
import com.google.common.collect.ImmutableList;
import org.codehaus.plexus.classworlds.launcher.ConfigurationException;
import java.util.Optional;
import java.util.function.IntFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
/**
* Parses rules of the form
* {@code com.foo.Bar -> sun.misc.Unsafe: WARN} and {@code com.fo.Bar on sun.misc.Unsafe: WARN}.
*/
public class ArrowRuleParser {
private static final String ERROR_MESSAGE_LINE_INVALID_RULE = "The line '%s' defines no valid rule.";
private static final String ERROR_MESSAGE_MULTIPLE_RULES =
"The line '%s' defines multiple rules. Please separate rules by a newline.";
/**
* Pattern to separate a string spanning multiple lines.
*/
private static final Pattern LINE_PATTERN = Pattern.compile("^", Pattern.MULTILINE);
/**
* Pattern to match a rule of the form {@code dependent -> dependency: severity}.
*/
private static final Pattern ARROW_RULE_PATTERN = Pattern.compile(
"\\s*(\\S*)\\s*" + Arrow.REGULAR_EXPRESSION_MATCHER + "\\s*(\\S*)\\s*:\\s*(\\S*)\\s*");
/**
* Format for {@code dependent -> dependency: severity}, where {@code dependent}, {@code dependency}, the arrow,
* and {@code severity} are provided as arguments to {@link String#format(String, Object...) String.format}.
*/
private static final String ARROW_RULE_FORMAT = "%s %s %s: %s";
/**
* Parses the rules in the specified string and returns a list of the created {@link DependencyRule}s.
*
* @param rules
* a string that defines dependency rules; individual rules must be separated by a newline; empty lines are
* allowed
*
* @return a list of parsed rules
*
* @throws ConfigurationException
* if a non-empty line could not be parsed
*/
public static ImmutableList<DependencyRule> parseRules(String rules) throws ConfigurationException {
requireNonNull(rules, "The argument 'rules' must not be null.");
if (rules.trim().isEmpty())
return ImmutableList.of();
ImmutableList.Builder<DependencyRule> ruleList = ImmutableList.builder();
for (String ruleLine : LINE_PATTERN.split(rules)) {
parseRuleLine(ruleLine).ifPresent(ruleList::add);
}
return ruleList.build();
}
private static Optional<DependencyRule> parseRuleLine(String ruleLine) throws ConfigurationException {
if (ruleLine.trim().isEmpty())
return Optional.empty();
Matcher ruleMatcher = ARROW_RULE_PATTERN.matcher(ruleLine);
final Optional<DependencyRule> rule;
if (ruleMatcher.find())
rule = Optional.of(extractRuleFromCurrentMatch(ruleMatcher::group));
else
throw new ConfigurationException(format(ERROR_MESSAGE_LINE_INVALID_RULE, ruleLine.trim()));
// disallow two rules in the same line
if (ruleMatcher.find())
throw new ConfigurationException(format(ERROR_MESSAGE_MULTIPLE_RULES, ruleLine.trim()));
return rule;
}
private static DependencyRule extractRuleFromCurrentMatch(IntFunction<String> getGroup)
throws ConfigurationException {
try {
return DependencyRule.of(getGroup.apply(1), getGroup.apply(3), getGroup.apply(4));
} catch (IllegalArgumentException ex) {
throw new ConfigurationException(ex.getMessage());
}
}
/**
* Formats the specified dependency rule as an arrow string that can later be parsed by this parser.
*
* @param arrow
* the arrow text to use
* @param rule
* the rule to convert to string
*
* @return an arrow rule string
*/
public static String ruleToArrowString(Arrow arrow, DependencyRule rule) {
requireNonNull(arrow, "The argument 'arrow' must not be null.");
requireNonNull(rule, "The argument 'rule' must not be null.");
return format(ARROW_RULE_FORMAT, rule.getDependent(), arrow.text(), rule.getDependency(), rule.getSeverity());
}
}