All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.design.ClassImportCouplingCheck Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012-2025 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * 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 Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.java.checks.design;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.java.checks.helpers.ClassPatternsUtils;
import org.sonar.java.checks.helpers.ExpressionsHelper;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.JavaTree;
import org.sonar.java.model.expression.IdentifierTreeImpl;
import org.sonar.plugins.java.api.JavaFileScannerContext;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.ImportTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.Tree;

@Rule(key = "S6539")
public class ClassImportCouplingCheck extends AbstractCouplingChecker {

  private static final int COUPLING_THRESHOLD = 20;
  @RuleProperty(
    key = "couplingThreshold",
    description = "Maximum number of classes a single class is allowed to depend upon. This value is an experimental value.",
    defaultValue = "" + COUPLING_THRESHOLD)
  public int couplingThreshold = COUPLING_THRESHOLD;
  private String packageName;
  private Set imports;
  private Set secondaryLocations;

  @Override
  public void scanFile(JavaFileScannerContext context) {
    super.scanFile(context);
    imports = null;
    secondaryLocations = null;
  }

  @Override
  public void visitClass(ClassTree tree) {
    // if class is utility or private inner class -> don't report
    if (ClassPatternsUtils.isUtilityClass(tree) || ClassPatternsUtils.isPrivateInnerClass(tree)) {
      return;
    }

    if (tree.is(Tree.Kind.CLASS) && tree.simpleName() != null) {
      nesting.push(types);
      types = new HashSet<>();
    }
    CompilationUnitTree compilationUnitTree = (CompilationUnitTree) ExpressionUtils.getParentOfType(tree, Tree.Kind.COMPILATION_UNIT);
    packageName = JavaTree.PackageDeclarationTreeImpl.packageNameAsString(compilationUnitTree.packageDeclaration());

    if (imports == null) {
      String fileProjectName = context.getProject().key();

      imports = compilationUnitTree.imports().stream()
        .filter(i -> !i.is(Tree.Kind.EMPTY_STATEMENT))
        .map(ImportTree.class::cast)
        .map(ImportTree::qualifiedIdentifier)
        .filter(i -> ExpressionsHelper.concatenate(((ExpressionTree) i)).startsWith(fileProjectName))
        .collect(Collectors.toSet());

      secondaryLocations = new HashSet<>();
      secondaryLocations.addAll(imports);
    }

    checkTypes(tree.superClass(), types);
    checkTypes(tree.superInterfaces());
    super.visitClass(tree);

    if (tree.is(Tree.Kind.CLASS) && tree.simpleName() != null) {
      int size = imports.size() + types.size();
      if (size > couplingThreshold) {
        context.reportIssue(this, tree.simpleName(), "Split this “Monster Class” into smaller and more specialized ones " +
          "to reduce its dependencies on other classes from " + size +
          " to the maximum authorized " + couplingThreshold + " or less.", getSecondaryLocations(), null);
      }
      types = nesting.pop();
    }
  }

  private List getSecondaryLocations() {
    return secondaryLocations.stream()
      .map(element -> new JavaFileScannerContext.Location("This class contributes to the tight class coupling.", element))
      .toList();
  }

  @Override
  public void checkTypes(@Nullable Tree type, @Nullable Set types) {
    if (type == null || types == null) {
      return;
    }
    if (type.is(Tree.Kind.IDENTIFIER)) {
      IdentifierTreeImpl identifierTree = (IdentifierTreeImpl) type;
      String fullyQualifiedName = identifierTree.symbolType().fullyQualifiedName();
      if (fullyQualifiedName.contains(packageName)) {
        types.add(fullyQualifiedName);
        secondaryLocations.add(type);
      }
    } else if (type.is(Tree.Kind.MEMBER_SELECT)) {
      MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) type;
      String name = ExpressionsHelper.concatenate(memberSelect);
      if (name.contains(packageName)) {
        types.add(name);
        secondaryLocations.add(type);
      }
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy