All Downloads are FREE. Search and download functionalities are using the official Maven repository.

guru.nidi.codeassert.dependency.DependencyRules Maven / Gradle / Ivy

There is a newer version: 0.9.15
Show newest version
/*
 * 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy