001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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    }