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 */
020package org.sonar.java.bytecode.check;
021
022import java.util.Map;
023
024import org.apache.commons.lang.StringUtils;
025import org.sonar.api.utils.WildcardPattern;
026import org.sonar.check.Cardinality;
027import org.sonar.check.Priority;
028import org.sonar.check.Rule;
029import org.sonar.check.RuleProperty;
030import org.sonar.java.PatternUtils;
031import org.sonar.java.bytecode.asm.AsmClass;
032import org.sonar.java.bytecode.asm.AsmEdge;
033import org.sonar.java.bytecode.asm.AsmMethod;
034import org.sonar.java.bytecode.visitor.BytecodeVisitor;
035import org.sonar.squid.api.CheckMessage;
036import org.sonar.squid.api.SourceFile;
037import org.sonar.squid.api.SourceMethod;
038
039import com.google.common.collect.Maps;
040
041@Rule(key = "ArchitecturalConstraint", cardinality = Cardinality.MULTIPLE, priority = Priority.MAJOR)
042public 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}