From 4309fc779ee20b873226cb3c0081f9d42145b967 Mon Sep 17 00:00:00 2001 From: Godin Date: Thu, 21 Oct 2010 11:27:13 +0000 Subject: [PATCH] SONAR-1832: Create an architecture rule engine --- .../bytecode/check/ArchitectureCheck.java | 85 +++++++++++++------ .../bytecode/check/ArchitectureCheckTest.java | 20 ++--- 2 files changed, 67 insertions(+), 38 deletions(-) diff --git a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/check/ArchitectureCheck.java b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/check/ArchitectureCheck.java index e51d241ce87..2ca7f47ee4f 100644 --- a/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/check/ArchitectureCheck.java +++ b/plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/bytecode/check/ArchitectureCheck.java @@ -28,71 +28,100 @@ import org.sonar.check.Priority; import org.sonar.java.bytecode.asm.AsmClass; import org.sonar.java.bytecode.asm.AsmEdge; import org.sonar.squid.api.CheckMessage; -import org.sonar.squid.api.SourceCodeEdgeUsage; import org.sonar.squid.api.SourceFile; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import java.util.List; +import java.util.Set; + @Check(key = "Dependency", title = "Respect rule architecture", isoCategory = IsoCategory.Portability, priority = Priority.MINOR, description = "

Links between classes must respect defined architecture rules.

") public class ArchitectureCheck extends BytecodeCheck { - @CheckProperty(title = "Pattern forbidden for from classes", key = "fromClasses") - private String fromClasses = new String(); + @CheckProperty(title = "Pattern forbidden for from classes", key = "fromPatterns") + private String fromPatterns = new String(); - @CheckProperty(title = "Pattern forbidden for to classes", key = "toClasses") - private String toClasses = new String(); + @CheckProperty(title = "Pattern forbidden for to classes", key = "toPatterns") + private String toPatterns = new String(); - public String getFromClasses() { - return fromClasses; - } + private List fromMatchers; + private List toMatchers; + private AsmClass asmClass; + private Set internalNames; - public void setFromClasses(String fromClasses) { - this.fromClasses = fromClasses; + public String getFromPatterns() { + return fromPatterns; } - public String getToClasses() { - return toClasses; + public void setFromPatterns(String patterns) { + this.fromPatterns = patterns; } - public void setToClasses(String toClasses) { - this.toClasses = toClasses; + public String getToPatterns() { + return toPatterns; } - private AsmClass asmClass; + public void setToPatterns(String patterns) { + this.toPatterns = patterns; + } @Override public void visitClass(AsmClass asmClass) { this.asmClass = asmClass; + this.internalNames = Sets.newHashSet(); } @Override public void visitEdge(AsmEdge edge) { if (edge != null) { - SourceCodeEdgeUsage usage = edge.getUsage(); - if (usage.equals(SourceCodeEdgeUsage.USES) || usage.equals(SourceCodeEdgeUsage.CALLS_METHOD) - || usage.equals(SourceCodeEdgeUsage.CALLS_FIELD) || usage.equals(SourceCodeEdgeUsage.CONTAINS)) { - String internalNameTargetClass = edge.getTargetAsmClass().getInternalName(); + String internalNameTargetClass = edge.getTargetAsmClass().getInternalName(); + if ( !internalNames.contains(internalNameTargetClass)) { String nameAsmClass = asmClass.getInternalName(); - if (matchesPattern(nameAsmClass, fromClasses) && matchesPattern(internalNameTargetClass, toClasses)) { + if (matches(nameAsmClass, getFromMatchers()) && matches(internalNameTargetClass, getToMatchers())) { SourceFile sourceFile = getSourceFile(asmClass); CheckMessage message = new CheckMessage(this, nameAsmClass + " shouldn't directly use " + internalNameTargetClass); message.setLine(edge.getSourceLineNumber()); sourceFile.log(message); + internalNames.add(internalNameTargetClass); } } } } - private boolean matchesPattern(String className, String pattern) { - if (StringUtils.isEmpty(pattern)) { - return true; - } - String[] patterns = pattern.split(","); - for (String p : patterns) { - p = StringUtils.replace(p, ".", "/"); - if (WildcardPattern.create(p).match(className)) { + private boolean matches(String className, List matchers) { + for (WildcardPattern matcher : matchers) { + if (matcher.match(className)) { return true; } } return false; } + private List createMatchers(String pattern) { + List matchers = Lists.newArrayList(); + if (StringUtils.isNotEmpty(pattern)) { + String[] patterns = pattern.split(","); + for (String p : patterns) { + p = StringUtils.replace(p, ".", "/"); + matchers.add(WildcardPattern.create(p)); + } + } + return matchers; + } + + private List getFromMatchers() { + if (fromMatchers == null) { + fromMatchers = createMatchers(fromPatterns); + } + return fromMatchers; + } + + private List getToMatchers() { + if (toMatchers == null) { + toMatchers = createMatchers(toPatterns); + } + return toMatchers; + } + } diff --git a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/check/ArchitectureCheckTest.java b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/check/ArchitectureCheckTest.java index f56e2843a60..7ebe0a05c49 100644 --- a/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/check/ArchitectureCheckTest.java +++ b/plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/bytecode/check/ArchitectureCheckTest.java @@ -19,10 +19,6 @@ */ package org.sonar.java.bytecode.check; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.sonar.java.ast.SquidTestUtils.getFile; - import org.junit.Test; import org.sonar.java.ast.JavaAstScanner; import org.sonar.java.bytecode.BytecodeScanner; @@ -31,13 +27,17 @@ import org.sonar.squid.Squid; import org.sonar.squid.api.CheckMessage; import org.sonar.squid.api.SourceFile; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.sonar.java.ast.SquidTestUtils.getFile; + public class ArchitectureCheckTest { private Squid squid; @Test public void testDependencyCheckOneErrorMessage() { - check("", "java.**.Pattern"); + check("*", "java.**.Pattern"); SourceFile file = (SourceFile) squid.search("ArchitectureCheckOneErrorMessage.java"); assertThat(file.getCheckMessages().size(), is(1)); @@ -48,10 +48,10 @@ public class ArchitectureCheckTest { @Test public void testDependencyCheckDateForbidden() { - check("", "**.Date"); + check("*", "**.Date"); SourceFile file = (SourceFile) squid.search("ArchitectureCheckDateForbidden.java"); - assertThat(file.getCheckMessages().size(), is(3)); + assertThat(file.getCheckMessages().size(), is(2)); // for (CheckMessage message : file.getCheckMessages()) { // System.out.println(message.getDefaultMessage()); // } @@ -62,7 +62,7 @@ public class ArchitectureCheckTest { check("*UI", "java.sql.*"); SourceFile file = (SourceFile) squid.search("ArchitectureCheckToSqlFromUI.java"); - assertThat(file.getCheckMessages().size(), is(7)); + assertThat(file.getCheckMessages().size(), is(4)); // for (CheckMessage message : file.getCheckMessages()) { // System.out.println(message.getDefaultMessage() + " at line " + message.getLine()); // } @@ -78,8 +78,8 @@ public class ArchitectureCheckTest { private void check(String fromClasses, String toClasses) { ArchitectureCheck check = new ArchitectureCheck(); - check.setFromClasses(fromClasses); - check.setToClasses(toClasses); + check.setFromPatterns(fromClasses); + check.setToPatterns(toClasses); squid = new Squid(new JavaSquidConfiguration()); squid.register(JavaAstScanner.class).scanDirectory(getFile("/bytecode/architecture/src")); -- 2.39.5