private static void createArchitectureDependency(DependencyGraph dependencyGraph, String tag, Dependency dep) { Node baseFrom = dep.getSource(); Node baseTo = dep.getTarget(); // if the tag is not set on either of these two, the Node will be null, too, and no dependency will be mapped: Node parentFrom = dependencyGraph.getNode((String) baseFrom.getProperty(tag + Constants.PARENT_SUFFIX)); Node parentTo = dependencyGraph.getNode((String) baseTo.getProperty(tag + Constants.PARENT_SUFFIX)); if (parentFrom != null && parentTo != null && !parentFrom.equals(parentTo)) { Dependency architectureDependency = dependencyGraph.addDependency(parentFrom, parentTo, dep.getDependencyType()); architectureDependency.addBaseDependency(dep); // if there is at least one base dependency which depends on an Impl class, it's marked as DEPENDS_ON_IMPL boolean isImpl = architectureDependency.getProperty(DEPENDS_ON_IMPL, false) || baseTo.getProperty(tag + Constants.IMPL_SUFFIX, false); architectureDependency.setProperty(DEPENDS_ON_IMPL, isImpl); architectureDependency.addListProperty(Constants.TARGET_API, baseTo.getProperty(tag + Constants.PARENT_API_SUFFIX)); } } }
private static void tagBaseRelationNumbers(Dependency dependency) { List<Dependency> baseDependencies = dependency.getBaseDependencies(); if (!baseDependencies.isEmpty()) { Set<String> sourceNames = baseDependencies.stream() .map(dep -> dep.getSource().getName()) .collect(Collectors.toSet()); Set<String> targetNames = baseDependencies.stream() .map(dep -> dep.getTarget().getName()) .collect(Collectors.toSet()); dependency.setProperty(Constants.BASE_REL_COUNT, baseDependencies.size()); dependency.setProperty(Constants.BASE_REL_COUNT_SOURCES, sourceNames.size()); dependency.setProperty(Constants.BASE_REL_COUNT_TARGETS, targetNames.size()); } } }
private void logViolatingDependencies() { if (!violatingDependencies.isEmpty()) { LOGGER.error("There are {} uncovered dependencies: ", violatingDependencies.size()); violatingDependencies.forEach(dep -> { LOGGER.error(" {}", dep); dep.getBaseDependencies().forEach(baseDep -> LOGGER.error(" * {}", baseDep)); addViolation(dep.toString()); } ); } }
/** * Checks if there is a rule which allows the given edge. * <p> * Checks for dependencies on implementation; logs all dependencies on implementation, including the relevant * base relations. * * @param edge the edge * @return true if there is at least one rule which allows the given edge. */ private boolean hasMatchingRule(Dependency edge) { Boolean dependsOnImpl = edge.getProperty(DependencyMapper.DEPENDS_ON_IMPL, false); if (dependsOnImpl) { implementationDependencies.add(edge); LOGGER.warn("Dependency on implementation instead of API: {}:", edge); edge.getBaseDependencies().stream() .filter(baseDep -> baseDep.getTarget().getProperty(implPropertyKey, false)) .forEach(baseDep -> LOGGER.warn(" * {}", baseDep)); } return isInSameScope(edge) || hasAllowedParentEdge(edge); }
private StringTemplate createEdgeST(Dependency dependency) { noEdges++; StringTemplate edgeST = templates.getInstanceOf(EDGE_ST); edgeST.setAttribute("from", GraphExportStyles.getId(dependency.getSource().getName())); edgeST.setAttribute("to", GraphExportStyles.getId(dependency.getTarget().getName())); edgeST.setAttribute("style", DotExportStyles.getEdgeStyle(dependency)); if (createEdgeLabels) { if (dependency.hasProperty(Constants.BASE_REL_COUNT)) { edgeST.setAttribute(LABEL_ATT, dependency.getProperty(Constants.BASE_REL_COUNT)); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_TARGETS)) { edgeST.setAttribute(HEAD_LABEL_ATT, dependency.getProperty(Constants.BASE_REL_COUNT_TARGETS)); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_SOURCES)) { edgeST.setAttribute(TAIL_LABEL_ATT, dependency.getProperty(Constants.BASE_REL_COUNT_SOURCES)); } } return edgeST; }
private void mapReference(Dependency dependency) { ReferencesRelation referencesRelation = new ReferencesRelation(); referencesRelation.setReferenceType(dependency.getDependencyType().name()); referencesRelation.setFrom(nodeMap.get(dependency.getSource().getName())); referencesRelation.setTo(nodeMap.get(dependency.getTarget().getName())); referencesRelation.setLineNo(dependency.getProperty("lineNo", new ArrayList<>())); mapProperties(referencesRelation.getProperties(), dependency.getProperties()); referencesRelation.getProperties().remove("lineNo"); referencesRelation.getFrom().getReferencesRelations().add(referencesRelation); referencesRelations.add(referencesRelation); }
private void mapHierarchy(Dependency dependency) { ArchitectureNode from = (ArchitectureNode) nodeMap.get(dependency.getSource().getName()); AbstractNode to = nodeMap.get(dependency.getTarget().getName()); if (to instanceof ArchitectureNode) { ArchitectureNode child = (ArchitectureNode) to; from.getChildren().add(child); child.setParent(from); } else { ClassNode impl = (ClassNode) to; from.getImplementations().add(impl); impl.getImplementationFor().add(from); } }
/** * provide the edge style depending on the type of the relationship. * * @param rel the {@link Dependency} to style * @return the edge {@link LineStyle} */ public static LineStyle getEdgeStyle(Dependency rel) { return EDGE_STYLES.getOrDefault(rel.getDependencyType(), DEFAULT_LINE_STYLE); }
private Node getParentNode(Node source) { Set<Dependency> incomingEdges = dependencyGraph.getIncomingEdges(source, DependencyType.CONTAINS); if (!incomingEdges.isEmpty()) { if (incomingEdges.size() > 1) { LOGGER.error("Node {} has more than one parent: {}", source.getName(), incomingEdges); } return incomingEdges.iterator().next().getSource(); } return null; }
@SuppressWarnings("unchecked") private Boolean isExplicitlyAllowed(Node source, Node target) { List<String> uses = (List<String>) source.getProperty(Constants.USES); Boolean result = uses != null && uses.contains(target.getName()); if (result) { source.addListProperty(Constants.USED_RULES, target.getName()); } Dependency edge = dependencyGraph.getEdge(source, target); if (edge != null) { List<String> referencedApis = (List<String>) edge.getProperty(Constants.TARGET_API); if (uses != null && referencedApis != null && uses.containsAll(referencedApis)) { referencedApis.forEach(targetName -> source.addListProperty(Constants.USED_RULES, targetName)); result = true; } } return result; }
private void addDependency(String targetClassName, DependencyType dependencyType) { if (!className.equals(targetClassName) && !isIgnorable(targetClassName)) { LOGGER.debug("Add dependency: {}#{} --[{}]--> {}", className, methodName, dependencyType, targetClassName); Node targetNode = dependencyGraph.getOrCreateNodeByName(targetClassName); targetNode.setProperty(TYPE, TYPE_CLASS); dependencyGraph.addDependency(classNode, targetNode, dependencyType).addListProperty(LINE_NO, lineNo); } } }
private StringTemplate createEdgeST(Dependency dependency) { noEdges++; StringTemplate edgeST = templates.getInstanceOf(EDGE_ST); edgeST.setAttribute("from", GraphExportStyles.getId(dependency.getSource().getName())); edgeST.setAttribute("to", GraphExportStyles.getId(dependency.getTarget().getName())); edgeST.setAttribute("style", getEdgeStyle(dependency)); if (createEdgeLabels) { if (dependency.hasProperty(Constants.BASE_REL_COUNT)) { edgeST.setAttribute(LABEL_ATT, dependency.getProperty(Constants.BASE_REL_COUNT)); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_TARGETS)) { edgeST.setAttribute(HEAD_LABEL_ATT, dependency.getProperty(Constants.BASE_REL_COUNT_TARGETS)); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_SOURCES)) { edgeST.setAttribute(TAIL_LABEL_ATT, dependency.getProperty(Constants.BASE_REL_COUNT_SOURCES)); } } return edgeST; }
private boolean isInSameScope(Dependency edge) { LOGGER.debug("Checking for same namespace access: {}", edge); Node source = edge.getSource(); String scopeName = architecture.getParentComponentName(edge.getTarget().getName()); Node scope = dependencyGraph.getNode(scopeName); if (scope == null) { throw new IllegalStateException("Scope " + scopeName + " has no corresponding node in the dependency graph."); } while (source != null) { if (source == scope) { LOGGER.debug("Same namespace for {}: true", edge); return true; } source = getParentNode(source); } LOGGER.debug("Same namespace for {}: false", edge); return false; }
/** * provide the edge style depending on the type of the relationship. * * @param rel the {@link Dependency} to style * @return the edge {@link LineStyle} */ public static LineStyle getEdgeStyle(Dependency rel) { return EDGE_STYLES.getOrDefault(rel.getDependencyType(), DEFAULT_LINE_STYLE); }
/** * Analyze the annotation, and put the dependencies into the {@link DependencyGraph}. * * @param dependencyGraph the {@link DependencyGraph} * @param classNode the node representing the source class * @param desc the ASM description * @param visible true if the annotation is visible at runtime; only used for logging * @param collapseInnerClasses whether to collapse inner classes * @param lineNo the line number */ public static void analyzeAnnotation(DependencyGraph dependencyGraph, Node classNode, String desc, boolean visible, boolean collapseInnerClasses, int lineNo) { String annotationTypeName = AsmUtil.toClassName(desc, collapseInnerClasses); LOGGER.debug("Original method annotation info: Desc: {}, Visible: {} => {}", desc, visible, annotationTypeName); Node annotationNode = dependencyGraph.getOrCreateNodeByName(annotationTypeName); annotationNode.setProperty(TYPE, TYPE_CLASS); annotationNode.setProperty(ANNOTATION, true); dependencyGraph.addDependency(classNode, annotationNode, DependencyType.ANNOTATED_BY).addListProperty(LINE_NO, lineNo); } }
private StringTemplate createEdgeST(Dependency dependency) { noEdges++; StringTemplate edgeST = templates.getInstanceOf(EDGE_ST); edgeST.setAttribute("from", GraphExportStyles.getId(dependency.getSource().getName())); edgeST.setAttribute("to", GraphExportStyles.getId(dependency.getTarget().getName())); LineStyle style = GraphExportStyles.getEdgeStyle(dependency); edgeST.setAttribute("color", style.getColor()); edgeST.setAttribute("style", style.getLineStyle()); edgeST.setAttribute("width", style.getWidth()); if (createEdgeLabels) { if (dependency.hasProperty(Constants.BASE_REL_COUNT)) { edgeST.setAttribute(EDGE_LABEL_ATT, createEdgeLabelST(dependency.getProperty(Constants.BASE_REL_COUNT, 0), Position.MIDDLE, style.getColor())); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_TARGETS)) { edgeST.setAttribute(EDGE_LABEL_ATT, createEdgeLabelST(dependency.getProperty(Constants.BASE_REL_COUNT_TARGETS, 0), Position.HEAD, style.getColor())); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_SOURCES)) { edgeST.setAttribute(EDGE_LABEL_ATT, createEdgeLabelST(dependency.getProperty(Constants.BASE_REL_COUNT_SOURCES, 0), Position.TAIL, style.getColor())); } } return edgeST; }
private boolean hasAllowedParentEdge(Dependency edge) { Node source = edge.getSource(); Node target = edge.getTarget(); Node targetParent = getParentNode(target); while (source != null && targetParent != null) { // also check relation to the parent because allowed dependencies are declared on the components, not on the leafs. LOGGER.debug("Checking: {} -> {} and {} -> {}", source.getName(), target.getName(), source.getName(), targetParent.getName()); if (isExplicitlyAllowed(source, target) || isExplicitlyAllowed(source, targetParent)) { return true; } source = getParentNode(source); } return false; }
private void check() { dependencyGraph.getAllEdges().stream() .filter(edge -> edge.getDependencyType() != DependencyType.CONTAINS) .filter(edge -> !hasMatchingRule(edge)) .forEach(violatingDependencies::add); logViolatingDependencies(); }
private StringTemplate createEdgeST(Dependency dependency) { noEdges++; StringTemplate edgeST = templates.getInstanceOf(EDGE_ST); edgeST.setAttribute("from", GraphExportStyles.getId(dependency.getSource().getName())); edgeST.setAttribute("to", GraphExportStyles.getId(dependency.getTarget().getName())); LineStyle style = GraphExportStyles.getEdgeStyle(dependency); edgeST.setAttribute("color", style.getColor()); edgeST.setAttribute("style", style.getLineStyle()); edgeST.setAttribute("width", style.getWidth()); if (createEdgeLabels) { if (dependency.hasProperty(Constants.BASE_REL_COUNT)) { edgeST.setAttribute(EDGE_LABEL_ATT, createEdgeLabelST(dependency.getProperty(Constants.BASE_REL_COUNT, 0), Position.MIDDLE, style.getColor())); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_TARGETS)) { edgeST.setAttribute(EDGE_LABEL_ATT, createEdgeLabelST(dependency.getProperty(Constants.BASE_REL_COUNT_TARGETS, 0), Position.HEAD, style.getColor())); } if (dependency.hasProperty(Constants.BASE_REL_COUNT_SOURCES)) { edgeST.setAttribute(EDGE_LABEL_ATT, createEdgeLabelST(dependency.getProperty(Constants.BASE_REL_COUNT_SOURCES, 0), Position.TAIL, style.getColor())); } } return edgeST; }
/** * Maps all dependencies given in the Base Graph onto the level of architecture components: For each Edge from V1 to * V2, it creates the dependency parent(V1) to parent(V2) in the Target Graph, where the parent nodes are defined by * the given tag. They should be set beforehand, see {@link ArchitectureNodeCreator}. * We assume that both parent(V1) and parent(V2) exist in the Target Graph. * * @param dependencyGraph the {@link DependencyGraph} * @param tag the name of the architecture */ public static void mapDependencies(DependencyGraph dependencyGraph, String tag) { dependencyGraph.getAllEdges() .stream() .filter(dep -> dep.getDependencyType() != DependencyType.CONTAINS) .forEach(edge -> createArchitectureDependency(dependencyGraph.getBaseGraph(), tag, edge)); }