@@ -0,0 +1,98 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2009 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
package org.sonar.java.bytecode.check; | |||
import org.apache.commons.lang.StringUtils; | |||
import org.sonar.api.utils.WildcardPattern; | |||
import org.sonar.check.Check; | |||
import org.sonar.check.CheckProperty; | |||
import org.sonar.check.IsoCategory; | |||
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; | |||
@Check(key = "Dependency", title = "Respect rule architecture", isoCategory = IsoCategory.Portability, priority = Priority.MINOR, description = "<p>Links between classes must respect defined architecture rules.</p>") | |||
public class ArchitectureCheck extends BytecodeCheck { | |||
@CheckProperty(title = "Pattern forbidden for from classes", key = "fromClasses") | |||
private String fromClasses = new String(); | |||
@CheckProperty(title = "Pattern forbidden for to classes", key = "toClasses") | |||
private String toClasses = new String(); | |||
public String getFromClasses() { | |||
return fromClasses; | |||
} | |||
public void setFromClasses(String fromClasses) { | |||
this.fromClasses = fromClasses; | |||
} | |||
public String getToClasses() { | |||
return toClasses; | |||
} | |||
public void setToClasses(String toClasses) { | |||
this.toClasses = toClasses; | |||
} | |||
private AsmClass asmClass; | |||
@Override | |||
public void visitClass(AsmClass asmClass) { | |||
this.asmClass = asmClass; | |||
} | |||
@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 nameAsmClass = asmClass.getInternalName(); | |||
if (matchesPattern(nameAsmClass, fromClasses) && matchesPattern(internalNameTargetClass, toClasses)) { | |||
SourceFile sourceFile = getSourceFile(asmClass); | |||
CheckMessage message = new CheckMessage(this, nameAsmClass + " shouldn't directly use " + internalNameTargetClass); | |||
message.setLine(edge.getSourceLineNumber()); | |||
sourceFile.log(message); | |||
} | |||
} | |||
} | |||
} | |||
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)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
} |
@@ -31,6 +31,7 @@ public final class BytecodeChecks { | |||
return Arrays.asList( | |||
(Class) CallToDeprecatedMethodCheck.class, | |||
UnusedPrivateMethodCheck.class, | |||
UnusedProtectedMethodCheck.class); | |||
UnusedProtectedMethodCheck.class, | |||
ArchitectureCheck.class); | |||
} | |||
} |
@@ -0,0 +1,89 @@ | |||
/* | |||
* Sonar, open source software quality management tool. | |||
* Copyright (C) 2009 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* Sonar is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* Sonar is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public | |||
* License along with Sonar; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | |||
*/ | |||
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; | |||
import org.sonar.java.squid.JavaSquidConfiguration; | |||
import org.sonar.squid.Squid; | |||
import org.sonar.squid.api.CheckMessage; | |||
import org.sonar.squid.api.SourceFile; | |||
public class ArchitectureCheckTest { | |||
private Squid squid; | |||
@Test | |||
public void testDependencyCheckOneErrorMessage() { | |||
check("", "java.**.Pattern"); | |||
SourceFile file = (SourceFile) squid.search("ArchitectureCheckOneErrorMessage.java"); | |||
assertThat(file.getCheckMessages().size(), is(1)); | |||
CheckMessage message = file.getCheckMessages().iterator().next(); | |||
assertThat(message.getDefaultMessage(), is("ArchitectureCheckOneErrorMessage shouldn't directly use java/util/regex/Pattern")); | |||
assertThat(message.getLine(), is(6)); | |||
} | |||
@Test | |||
public void testDependencyCheckDateForbidden() { | |||
check("", "**.Date"); | |||
SourceFile file = (SourceFile) squid.search("ArchitectureCheckDateForbidden.java"); | |||
assertThat(file.getCheckMessages().size(), is(3)); | |||
// for (CheckMessage message : file.getCheckMessages()) { | |||
// System.out.println(message.getDefaultMessage()); | |||
// } | |||
} | |||
@Test | |||
public void testDependencyCheckToSqlFromUI() { | |||
check("*UI", "java.sql.*"); | |||
SourceFile file = (SourceFile) squid.search("ArchitectureCheckToSqlFromUI.java"); | |||
assertThat(file.getCheckMessages().size(), is(7)); | |||
// for (CheckMessage message : file.getCheckMessages()) { | |||
// System.out.println(message.getDefaultMessage() + " at line " + message.getLine()); | |||
// } | |||
} | |||
@Test | |||
public void testDependencyCheckOKFromClassesToClasses() { | |||
check("*SA", "java.sql.*"); | |||
SourceFile file = (SourceFile) squid.search("ArchitectureCheckToSqlFromUI.java"); | |||
assertThat(file.getCheckMessages().size(), is(0)); | |||
} | |||
private void check(String fromClasses, String toClasses) { | |||
ArchitectureCheck check = new ArchitectureCheck(); | |||
check.setFromClasses(fromClasses); | |||
check.setToClasses(toClasses); | |||
squid = new Squid(new JavaSquidConfiguration()); | |||
squid.register(JavaAstScanner.class).scanDirectory(getFile("/bytecode/architecture/src")); | |||
squid.registerVisitor(check); | |||
squid.register(BytecodeScanner.class).scanDirectory(getFile("/bytecode/architecture/bin")); | |||
} | |||
} |
@@ -0,0 +1,23 @@ | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<version>0.1-SNAPSHOT</version> | |||
<artifactId>sonar-bytecode</artifactId> | |||
<packaging>jar</packaging> | |||
<build> | |||
<sourceDirectory>src</sourceDirectory> | |||
<outputDirectory>bin</outputDirectory> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.apache.maven.plugins</groupId> | |||
<artifactId>maven-compiler-plugin</artifactId> | |||
<configuration> | |||
<target>1.5</target> | |||
<source>1.5</source> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,11 @@ | |||
import java.sql.Date; | |||
import java.util.Calendar; | |||
public class ArchitectureCheckDateForbidden { | |||
public ArchitectureCheckDateForbidden() { | |||
Date dateSql = new Date(200000); | |||
java.util.Date dateUtil = Calendar.getInstance().getTime(); | |||
long time = dateUtil.getTime(); | |||
} | |||
} |
@@ -0,0 +1,8 @@ | |||
import java.util.regex.Pattern; | |||
public class ArchitectureCheckOneErrorMessage { | |||
public ArchitectureCheckOneErrorMessage() { | |||
Pattern.compile("*.java"); | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
import java.sql.Connection; | |||
import java.sql.ResultSet; | |||
import java.sql.SQLException; | |||
import java.sql.Statement; | |||
public class ArchitectureCheckToSqlFromUI { | |||
ResultSet result; | |||
Connection connection; | |||
public ArchitectureCheckToSqlFromUI(Statement statement, String requete) { | |||
try { | |||
connection = statement.getConnection(); | |||
result = statement.executeQuery(requete); | |||
} catch (SQLException sql) { | |||
sql.printStackTrace(); | |||
} | |||
} | |||
} |