001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.java.bytecode.check;
021
022 import java.util.Map;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.sonar.api.utils.WildcardPattern;
026 import org.sonar.check.Cardinality;
027 import org.sonar.check.Priority;
028 import org.sonar.check.Rule;
029 import org.sonar.check.RuleProperty;
030 import org.sonar.java.PatternUtils;
031 import org.sonar.java.bytecode.asm.AsmClass;
032 import org.sonar.java.bytecode.asm.AsmEdge;
033 import org.sonar.java.bytecode.asm.AsmMethod;
034 import org.sonar.java.bytecode.visitor.BytecodeVisitor;
035 import org.sonar.squid.api.CheckMessage;
036 import org.sonar.squid.api.SourceFile;
037 import org.sonar.squid.api.SourceMethod;
038
039 import com.google.common.collect.Maps;
040
041 @Rule(key = "ArchitecturalConstraint", cardinality = Cardinality.MULTIPLE, priority = Priority.MAJOR)
042 public class ArchitectureCheck extends BytecodeVisitor {
043
044 @RuleProperty
045 private String fromClasses = "";
046
047 @RuleProperty
048 private String toClasses = "";
049
050 private WildcardPattern[] fromPatterns;
051 private WildcardPattern[] toPatterns;
052 private AsmClass asmClass;
053 private Map<String, CheckMessage> internalNames;
054
055 public String getFromClasses() {
056 return fromClasses;
057 }
058
059 public void setFromClasses(String patterns) {
060 this.fromClasses = patterns;
061 }
062
063 public String getToClasses() {
064 return toClasses;
065 }
066
067 public void setToClasses(String patterns) {
068 this.toClasses = patterns;
069 }
070
071 @Override
072 public void visitClass(AsmClass asmClass) {
073 String nameAsmClass = asmClass.getInternalName();
074 if (WildcardPattern.match(getFromPatterns(), nameAsmClass)) {
075 this.asmClass = asmClass;
076 this.internalNames = Maps.newHashMap();
077 } else {
078 this.asmClass = null;
079 }
080 }
081
082 @Override
083 public void leaveClass(AsmClass asmClass) {
084 if (this.asmClass != null) {
085 for (CheckMessage message : internalNames.values()) {
086 SourceFile sourceFile = getSourceFile(asmClass);
087 sourceFile.log(message);
088 }
089 }
090 }
091
092 @Override
093 public void visitEdge(AsmEdge edge) {
094 if (asmClass != null && edge != null) {
095 String internalNameTargetClass = edge.getTargetAsmClass().getInternalName();
096 if ( !internalNames.containsKey(internalNameTargetClass)) {
097 if (WildcardPattern.match(getToPatterns(), internalNameTargetClass)) {
098 int sourceLineNumber = getSourceLineNumber(edge);
099 logMessage(asmClass.getInternalName(), internalNameTargetClass, sourceLineNumber);
100 }
101 } else {
102 int sourceLineNumber = getSourceLineNumber(edge);
103 // we log only first occurrence with non-zero line number if exists
104 if (internalNames.get(internalNameTargetClass).getLine() == 0 && sourceLineNumber != 0) {
105 logMessage(asmClass.getInternalName(), internalNameTargetClass, sourceLineNumber);
106 }
107 }
108 }
109 }
110
111 private int getSourceLineNumber(AsmEdge edge) {
112 if ((edge.getSourceLineNumber() == 0) && (edge.getFrom() instanceof AsmMethod)) {
113 SourceMethod sourceMethod = getSourceMethod((AsmMethod) edge.getFrom());
114 if (sourceMethod != null) {
115 return sourceMethod.getStartAtLine();
116 }
117 }
118 return edge.getSourceLineNumber();
119 }
120
121 private void logMessage(String fromClass, String toClass, int sourceLineNumber) {
122 CheckMessage message = new CheckMessage(this, fromClass + " must not use " + toClass);
123 message.setLine(sourceLineNumber);
124 internalNames.put(toClass, message);
125 }
126
127 private WildcardPattern[] getFromPatterns() {
128 if (fromPatterns == null) {
129 fromPatterns = PatternUtils.createPatterns(StringUtils.defaultIfEmpty(fromClasses, "**"));
130 }
131 return fromPatterns;
132 }
133
134 private WildcardPattern[] getToPatterns() {
135 if (toPatterns == null) {
136 toPatterns = PatternUtils.createPatterns(toClasses);
137 }
138 return toPatterns;
139 }
140
141 }