Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
guru.nidi.codeassert.dependency.DependencyRules Maven / Gradle / Ivy
/*
* Copyright © 2015 Stefan Niederhauser ([email protected] )
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package guru.nidi.codeassert.dependency;
import guru.nidi.codeassert.model.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public final class DependencyRules {
private static final Logger LOG = LoggerFactory.getLogger(DependencyRules.class);
private static final Pattern ANONYMOUS_CLASS = Pattern.compile(".*?\\$\\d+");
private static final ThreadLocal CURRENT = new ThreadLocal<>();
private final List rules;
private final boolean allowAll;
private final boolean allowIntraPackageCycles;
final boolean allowIntraPackageDeps;
private DependencyRules(List rules, boolean allowAll,
boolean allowIntraPackageCycles, boolean allowIntraPackageDeps) {
this.rules = rules;
this.allowAll = allowAll;
this.allowIntraPackageCycles = allowIntraPackageCycles;
this.allowIntraPackageDeps = allowIntraPackageDeps;
}
public static DependencyRules allowAll() {
return new DependencyRules(new ArrayList<>(), true, true, true);
}
public static DependencyRules denyAll() {
return new DependencyRules(new ArrayList<>(), false, false, false);
}
public DependencyRules allowIntraPackageCycles(boolean allowIntraPackageCycles) {
return new DependencyRules(rules, allowAll, allowIntraPackageCycles, allowIntraPackageDeps);
}
public DependencyRules allowIntraPackageDependencies(boolean allowIntraPackageDeps) {
return new DependencyRules(rules, allowAll, allowIntraPackageCycles, allowIntraPackageDeps);
}
public DependencyRule addRule(String pack) {
return addRule(rule(pack));
}
public DependencyRule addRule(DependencyRule pack) {
rules.add(pack);
return pack;
}
/**
* An external package has a default {@code mayBeUsedBy("*")}.
*
* @param pack The name of the external package.
* @return A DependencyRule with the given name.
*/
public DependencyRule addExternal(String pack) {
final DependencyRule rule = rule(pack).optional();
rule.mayBeUsedBy(rule("*"));
return addRule(rule);
}
/**
* Add rules defined by a DependencyRuler class.
* The following DependencyRules are all equal:
*
* DependencyRules rules1 = DependencyRules.allowAll();
* DependencyRule a = rules1.addRule("com.acme.a.*"));
* DependencyRule b = rules1.addRule("com.acme.sub.b"));
* a.mustNotUse(b);
*
* ----
*
* class ComAcme implements DependencyRuler{
* DependencyRule a_, subB;
*
* public defineRules(){
* a_.mustNotUse(subB);
* }
* }
* DependencyRules rules2 = DependencyRules.allowAll().addRules(new ComAcme());
*
* ----
*
* DependencyRules rules3 = DependencyRules.allowAll().addRules("com.acme", new DependencyRuler(){
* DependencyRule a_, subB;
*
* public defineRules(){
* a_.mustNotUse(subB);
* }
* });
*
*
* @param basePackage the package to be prepended in front of the ruler's name
* @param ruler defines the dependency rules
* @return DependencyRules including the new rules.
*/
public DependencyRules withRules(String basePackage, DependencyRuler ruler) {
return doWithRules(addPackages(basePackage, ruler.getClass()), false, ruler);
}
public DependencyRules withAbsoluteRules(DependencyRuler... rulers) {
return doWithRules(false, false, rulers);
}
public DependencyRules withRelativeRules(DependencyRuler... rulers) {
return doWithRules(true, false, rulers);
}
public DependencyRules withExternals(DependencyRuler... rulers) {
return doWithRules(false, true, rulers);
}
public DependencyRules withExternals(String... externals) {
for (final String external : externals) {
addExternal(external);
}
return this;
}
private DependencyRules doWithRules(boolean withRulerName, boolean external, DependencyRuler... rulers) {
for (final DependencyRuler ruler : rulers) {
doWithRules(addPackages("", withRulerName ? ruler.getClass() : null), external, ruler);
}
return this;
}
private DependencyRules doWithRules(String basePackage, boolean external, DependencyRuler ruler) {
CURRENT.set(this);
try {
final List ruleFields = initFields(basePackage, ruler);
if (basePackage.length() > 0) {
ruler.base = rule(addPackages(basePackage, ""));
}
ruler.unnamed = rule(Model.UNNAMED_PACKAGE);
ruler.all = rule(addPackages(basePackage, "*"));
ruler.defineRules();
postProcessFields(ruleFields, external);
return this;
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Could not access field", e);
} finally {
CURRENT.remove();
}
}
static DependencyRule addRuleToCurrent(DependencyRule rule) {
final DependencyRules rules = DependencyRules.CURRENT.get();
if (rules != null) {
rules.addRule(rule);
}
return rule;
}
private List initFields(String basePackage, DependencyRuler ruler) throws IllegalAccessException {
final List ruleFields = new ArrayList<>();
for (final Field f : ruler.getClass().getDeclaredFields()) {
f.setAccessible(true);
if (f.getType() == CodeElement.class) {
ruleFields.add(initField(basePackage, ruler, f));
}
if (f.getType() == DependencyRule.class) {
ruleFields.add(addRule(initField(basePackage, ruler, f)));
}
}
return ruleFields;
}
private DependencyRule initField(String basePackage, DependencyRuler ruler, Field f) throws IllegalAccessException {
final CodeElement value = (CodeElement) f.get(ruler);
if (value instanceof DependencyRule && !"*".equals(value.pattern.getPattern())) {
return (DependencyRule) value;
}
final String name = f.getName();
deprecationWarnings(name);
final String pack = addPackages(basePackage, "$self".equals(name) ? "" : camelCaseToDotCase(name));
final DependencyRule rule = rule(pack);
f.set(ruler, rule);
return rule;
}
private void deprecationWarnings(String name) {
if ("$self".equals(name)) {
LOG.warn("'DependencyRule $self': $self is deprecated. Use base() instead.");
}
if ("_".equals(name)) {
LOG.warn("'DependencyRule _': _ is deprecated. Use all() instead.");
}
}
private void postProcessFields(List ruleFields, boolean external) {
if (external) {
for (final DependencyRule rule : ruleFields) {
if (rule.isEmpty()) {
rule.mayBeUsedBy(rule("*"));
}
}
}
}
private String addPackages(String base, Class> clazz) {
if (clazz == null) {
return addPackages(base, "");
}
if (isAnonymous(clazz)) {
return addPackages(base.length() > 0 ? base : clazz.getPackage().getName(), "");
}
return addPackages(base, camelCaseToDotCase(reallySimpleName(clazz)));
}
private String addPackages(String p1, String p2) {
return p1.length() > 0 && !p1.endsWith(".") && p2.length() > 0
? p1 + "." + p2
: p1 + p2;
}
private boolean isAnonymous(Class> clazz) {
//anonymous local classes return false to isAnonymousClass()
return clazz.isAnonymousClass() || ANONYMOUS_CLASS.matcher(clazz.getSimpleName()).matches();
}
private String reallySimpleName(Class> clazz) {
final String simple = clazz.getSimpleName();
final String prefix = clazz.getEnclosingMethod() == null ? "" : clazz.getEnclosingMethod().getName() + "$";
return simple.startsWith(prefix) ? simple.substring(prefix.length()) : simple;
}
private static String camelCaseToDotCase(String s) {
final StringBuilder res = new StringBuilder();
final boolean dollarMode = s.contains("$");
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
if (c == '_' && i == s.length() - 1) {
res.append(res.length() == 0 || res.charAt(res.length() - 1) == '.' ? "*" : ".*");
} else {
res.append(processChar(dollarMode, i == 0, c));
}
}
return res.toString();
}
public DependencyRule rule(String pattern) {
return new DependencyRule(pattern, allowAll);
}
private static String processChar(boolean dollarMode, boolean firstChar, char c) {
if (dollarMode) {
if (c == '$' && !firstChar) {
return ".";
}
return Character.toString(c);
}
if (Character.isUpperCase(c)) {
return (firstChar ? "" : ".") + Character.toLowerCase(c);
}
return Character.toString(c);
}
public > Dependencies analyzeRules(Scope scope) {
final Dependencies result = new Dependencies();
for (final DependencyRule rule : rules) {
result.merge(rule.analyzer(scope, this).analyze());
}
for (final T elem : scope) {
if (!elem.matchesAny(rules)) {
result.undefined.add(elem.getName());
}
}
result.normalize();
result.cycles.addAll(new Tarjan().analyzeCycles(scope, allowIntraPackageCycles));
return result;
}
> int mostSpecificUsageMatch(T from, T to, RuleAccessor accessor) {
int s = 0;
for (final DependencyRule rule : rules) {
if (rule.matches(to)) {
s = Math.max(s, from.mostSpecificMatch(accessor.access(rule)));
}
}
return s;
}
}