org.sonar.php.checks.ClassCouplingCheck.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.php.checks.ClassCouplingCheck.java

Source

/*
 * SonarQube PHP Plugin
 * Copyright (C) 2010-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.php.checks;

import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.php.parser.LexicalConstant;
import org.sonar.php.tree.impl.PHPTree;
import org.sonar.plugins.php.api.tree.Tree;
import org.sonar.plugins.php.api.tree.Tree.Kind;
import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ClassMemberTree;
import org.sonar.plugins.php.api.tree.declaration.ClassTree;
import org.sonar.plugins.php.api.tree.declaration.MethodDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree;
import org.sonar.plugins.php.api.tree.declaration.ParameterTree;
import org.sonar.plugins.php.api.tree.expression.AnonymousClassTree;
import org.sonar.plugins.php.api.tree.expression.ExpressionTree;
import org.sonar.plugins.php.api.tree.expression.FunctionCallTree;
import org.sonar.plugins.php.api.tree.expression.NewExpressionTree;
import org.sonar.plugins.php.api.tree.lexical.SyntaxToken;
import org.sonar.plugins.php.api.tree.lexical.SyntaxTrivia;
import org.sonar.plugins.php.api.visitors.PHPVisitorCheck;
import org.sonar.squidbridge.annotations.SqaleConstantRemediation;

@Rule(key = ClassCouplingCheck.KEY, name = "Classes should not be coupled to too many other classes (Single Responsibility Principle)", priority = Priority.MAJOR, tags = {
        Tags.BRAIN_OVERLOAD })
@SqaleConstantRemediation("2h")
public class ClassCouplingCheck extends PHPVisitorCheck {

    public static final String KEY = "S1200";
    private static final String MESSAGE = "Split this class into smaller and more specialized ones "
            + "to reduce its dependencies on other classes from %s to the maximum authorized %s or less.";

    public static final int DEFAULT = 20;
    private Deque<Set<String>> types = new ArrayDeque<>();
    private static final Set<String> DOC_TAGS = ImmutableSet.of("@var", "@global", "@staticvar", "@throws",
            "@param", "@return");

    private static final Set<String> EXCLUDED_TYPES = Sets.newTreeSet(String.CASE_INSENSITIVE_ORDER);

    static {
        EXCLUDED_TYPES.addAll(ImmutableSet.of("INTEGER", "INT", "DOUBLE", "FLOAT", "STRING", "ARRAY", "OBJECT",
                "BOOLEAN", "BOOL", "BINARY", "NULL", "MIXED"));
    }

    @RuleProperty(key = "max", defaultValue = "" + DEFAULT)
    public int max = DEFAULT;

    @Override
    public void visitNewExpression(NewExpressionTree tree) {
        retrieveInstantiatedClassName(tree);

        super.visitNewExpression(tree);
    }

    @Override
    public void visitClassDeclaration(ClassDeclarationTree tree) {
        if (tree.is(Kind.CLASS_DECLARATION)) {
            enterClass(tree);
        }

        super.visitClassDeclaration(tree);

        if (tree.is(Kind.CLASS_DECLARATION)) {
            leaveClass(tree);
        }
    }

    private void leaveClass(ClassTree tree) {
        int nbType = types.removeLast().size();

        if (nbType > max) {
            String message = String.format(MESSAGE, nbType, max);
            context().newIssue(this, message).tree(tree);
        }
    }

    private void enterClass(ClassTree tree) {
        types.addLast(new HashSet<String>());
        retrieveCoupledTypes(tree);
    }

    @Override
    public void visitAnonymousClass(AnonymousClassTree tree) {
        enterClass(tree);

        super.visitAnonymousClass(tree);

        leaveClass(tree);
    }

    private void retrieveCoupledTypes(ClassTree classTree) {
        for (ClassMemberTree classMember : classTree.members()) {
            switch (classMember.getKind()) {
            case CLASS_PROPERTY_DECLARATION:
            case CLASS_CONSTANT_PROPERTY_DECLARATION:
                retrieveTypeFromDoc(classMember);
                break;
            case METHOD_DECLARATION:
                retrieveTypeFromDoc(classMember);
                retrieveTypeFromParameter((MethodDeclarationTree) classMember);
                break;
            default:
                break;
            }
        }
    }

    private void retrieveTypeFromParameter(MethodDeclarationTree methodDeclaration) {
        for (ParameterTree parameter : methodDeclaration.parameters().parameters()) {
            Tree type = parameter.type();
            if (type != null && type.is(Kind.NAMESPACE_NAME)) {
                addType(getTypeName((NamespaceNameTree) type));
            }
        }
    }

    private void retrieveTypeFromDoc(ClassMemberTree varDeclaration) {
        SyntaxToken varDecToken = ((PHPTree) varDeclaration).getFirstToken();

        for (SyntaxTrivia comment : varDecToken.trivias()) {
            for (String line : comment.text().split("[" + LexicalConstant.LINE_TERMINATOR + "]++")) {
                retrieveTypeFromCommentLine(line);
            }
        }
    }

    private void retrieveTypeFromCommentLine(String line) {
        String[] commentLine = line.trim().split("[" + LexicalConstant.WHITESPACE + "]++");

        if (commentLine.length > 2 && DOC_TAGS.contains(commentLine[1])) {
            for (String type : commentLine[2].split("\\|")) {
                type = StringUtils.removeEnd(type, "[]");

                if (!EXCLUDED_TYPES.contains(type)) {
                    addType(type);
                }
            }
        }
    }

    private void retrieveInstantiatedClassName(NewExpressionTree newExpression) {
        ExpressionTree expression = newExpression.expression();

        if (expression.is(Kind.FUNCTION_CALL)) {
            ExpressionTree callee = ((FunctionCallTree) expression).callee();

            if (callee.is(Kind.NAMESPACE_NAME)) {
                addType(getTypeName((NamespaceNameTree) callee));
            }

        } else if (expression.is(Kind.NAMESPACE_NAME)) {
            addType(getTypeName((NamespaceNameTree) expression));
        }
    }

    private static String getTypeName(NamespaceNameTree namespaceName) {
        String name = namespaceName.fullName();
        String prefix = "namespace\\";
        if (StringUtils.startsWithIgnoreCase(name, prefix)) {
            // fixme (SONARPHP-552): Handle namespaces properly
            name = name.substring(prefix.length() - 1);
        }

        return name;
    }

    private void addType(String type) {
        if (!types.isEmpty()) {
            types.getLast().add(type);
        }
    }

}