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

com.hello2morrow.sonarplugin.processor.CycleGroupProcessor Maven / Gradle / Ivy

Go to download

Provides architecture governance features accompanied by metrics about cyclic dependencies and other structural aspects.

The newest version!
/*
 * Sonar Sonargraph Plugin
 * Copyright (C) 2009, 2010, 2011 hello2morrow GmbH
 * mailto: info AT hello2morrow DOT com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.hello2morrow.sonarplugin.processor;

import com.hello2morrow.sonarplugin.foundation.SonargraphPluginBase;
import com.hello2morrow.sonarplugin.foundation.Utilities;
import com.hello2morrow.sonarplugin.persistence.PersistenceUtilities;
import com.hello2morrow.sonarplugin.xsd.ReportContext;
import com.hello2morrow.sonarplugin.xsd.XsdAttributeRoot;
import com.hello2morrow.sonarplugin.xsd.XsdCycleGroup;
import com.hello2morrow.sonarplugin.xsd.XsdCycleGroups;
import com.hello2morrow.sonarplugin.xsd.XsdCyclePath;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issuable.IssueBuilder;
import org.sonar.api.profiles.RulesProfile;
import org.sonar.api.resources.Directory;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.rules.ActiveRule;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CycleGroupProcessor implements IProcessor {

  private static final String PHYSICAL_PACKAGE_NAMED_ELEMENT_GROUP = "Physical package";
  private static final String DIRECTORY_NAMED_ELEMENT_GROUP = "Directory";

  private static final Logger LOG = LoggerFactory.getLogger(CycleGroupProcessor.class);
  private double cyclicity = 0;
  private double biggestCycleGroupSize = 0;
  private double cyclicPackages = 0;
  private final ResourcePerspectives perspectives;
  private final Project project;
  private final ActiveRule rule;
  private final FileSystem fileSystem;
  private String sonargraphBasePath;

  public CycleGroupProcessor(Project project, final RulesProfile rulesProfile, final ResourcePerspectives perspectives, FileSystem fileSystem) {
    this.project = project;
    this.perspectives = perspectives;
    this.rule = rulesProfile.getActiveRule(SonargraphPluginBase.PLUGIN_KEY, SonargraphPluginBase.CYCLE_GROUP_RULE_KEY);
    this.fileSystem = fileSystem;
  }

  public double getCyclicity() {
    return cyclicity;
  }

  public double getBiggestCycleGroupSize() {
    return biggestCycleGroupSize;
  }

  public double getCyclicPackages() {
    return cyclicPackages;
  }

  @Override
  public void process(ReportContext report, XsdAttributeRoot buildUnit) {
    if (rule == null) {
      return;
    }

    this.sonargraphBasePath = PersistenceUtilities.getSonargraphBasePath(report);

    cyclicity = 0;
    biggestCycleGroupSize = 0;
    cyclicPackages = 0;
    boolean packageNotFound = false;

    FilePredicates predicates = fileSystem.predicates();
    XsdCycleGroups cycleGroups = report.getCycleGroups();

    for (XsdCycleGroup group : cycleGroups.getCycleGroup()) {
      if (!PersistenceUtilities.getBuildUnitName(group).equals(Utilities.getBuildUnitName(buildUnit.getName()))) {
        continue;
      }

      String namedElementGroup = group.getNamedElementGroup();
      packageNotFound = !createCycleGroupIssue(predicates, group, namedElementGroup) || packageNotFound;
    }

    if (packageNotFound) {
      LOG.warn("Issues not created for all packages involved in cycles. "
        + "Check if configuration for Sonargraph system includes the generation of cycle warnings for source files and directories!");
    }
  }

  /**
   * @return true if issue is created for all elements, false otherwise
   */
  private boolean createCycleGroupIssue(final FilePredicates predicates, final XsdCycleGroup group, final String namedElementGroup) {
    if (PHYSICAL_PACKAGE_NAMED_ELEMENT_GROUP.equals(namedElementGroup)) {
      int groupSize = group.getCyclePath().size();
      cyclicPackages += groupSize;
      cyclicity += groupSize * groupSize;
      if (groupSize > biggestCycleGroupSize) {
        biggestCycleGroupSize = groupSize;
      }
    } else if (DIRECTORY_NAMED_ELEMENT_GROUP.equals(namedElementGroup) && !handlePackageCycleGroup(group, predicates)) {
      return false;
    } else if ("Source file".equals(namedElementGroup)) {
      handleSourceFileGroup(group);
    }
    return true;
  }

  private void handleSourceFileGroup(XsdCycleGroup group) {
    List srcFiles = determineCyclicSrcFiles(group);
    for (Resource srcFile : srcFiles) {
      addCycleIssue(srcFile, srcFiles);
    }
  }

  private List determineCyclicSrcFiles(XsdCycleGroup group) {
    List srcFiles = new ArrayList();
    for (XsdCyclePath pathElement : group.getCyclePath()) {
      String cyclicFilePathRelative = Utilities.getSourceFilePath(group.getParent(), pathElement.getParent());
      if (cyclicFilePathRelative == null) {
        LOG.error("Failed to determine relative path within system for cycleGroupParent '" + group.getParent() + "' and source file '" + pathElement.getParent() + "'");
        continue;
      }

      String cyclicFilePathAbsolute = null;
      try {
        cyclicFilePathAbsolute = new File(this.sonargraphBasePath, cyclicFilePathRelative).getCanonicalPath().replace('\\', '/');
      } catch (IOException e1) {
        LOG.error("Failed to determine absolute path for '" + cyclicFilePathRelative + "'", e1);
      }

      if (cyclicFilePathAbsolute != null) {
        org.sonar.api.resources.File srcFile = org.sonar.api.resources.File.fromIOFile(new File(cyclicFilePathAbsolute), project);
        if (srcFile != null) {
          srcFiles.add(srcFile);
        } else {
          LOG.error("Failed to resolve resource of '" + cyclicFilePathAbsolute + "'");
        }
      }
    }

    return srcFiles;
  }

  private boolean handlePackageCycleGroup(XsdCycleGroup group, FilePredicates predicates) {
    List srcDirectories = determineCyclicSrcDirectories(group, predicates);
    boolean issueAddedForAllPackages = true;

    // No source directories are detected for class files
    for (Resource jPackage : srcDirectories) {
      issueAddedForAllPackages = addCycleIssue(jPackage, srcDirectories) || issueAddedForAllPackages;
    }
    return issueAddedForAllPackages;
  }

  private List determineCyclicSrcDirectories(XsdCycleGroup group, FilePredicates predicates) {
    List packages = new ArrayList();
    for (XsdCyclePath pathElement : group.getCyclePath()) {
      String cyclicPath;
      try {
        cyclicPath = new File(this.sonargraphBasePath, pathElement.getParent()).getCanonicalPath().replace('\\', '/');
      } catch (IOException e1) {
        LOG.error("Failed to determine absolute path for '" + pathElement.getParent() + "'", e1);
        return Collections.emptyList();
      }

      Set srcDirs = getSourceDirectories(predicates, cyclicPath);
      if (srcDirs.isEmpty()) {
        LOG.debug("Could not locate src directory for '" + pathElement.getParent() + "'");
        continue;
      }
      if (srcDirs.size() > 1) {
        LOG.warn("Found more than one src directory for '" + pathElement.getParent() + "'");
      }

      for (String path : srcDirs) {
        Directory srcDirectory = org.sonar.api.resources.Directory.fromIOFile(new File(path), project);
        if (srcDirectory != null) {
          packages.add(srcDirectory);
        } else {
          LOG.warn("Failed to resolve path '" + path + "' to directory.");
        }
      }
    }
    return packages;
  }

  private Set getSourceDirectories(FilePredicates predicates, String cyclicPath) {
    Set srcDirs = new HashSet();

    for (File next : fileSystem.files(predicates.and())) {
      File dir = next.getParentFile();
      try {
        String canonicalPath = dir.getCanonicalPath();
        if (canonicalPath.replace('\\', '/').endsWith(cyclicPath)) {
          srcDirs.add(canonicalPath);
        }
      } catch (IOException e) {
        LOG.warn("Could not get canonical path for directory '" + dir.getAbsolutePath() + "'", e);
      }
    }
    return srcDirs;
  }

  private boolean addCycleIssue(Resource resource, List involvedResources) {
    Issuable issuable = perspectives.as(Issuable.class, resource);
    if (issuable == null) {
      // omit cyclic directory that is part of package of java class folder
      return false;
    }
    IssueBuilder issueBuilder = issuable.newIssueBuilder();
    issueBuilder.severity(rule.getSeverity().toString()).ruleKey(rule.getRule().ruleKey());

    List tempInvolvedResources = new ArrayList(involvedResources);
    tempInvolvedResources.remove(resource);
    StringBuilder builder = new StringBuilder();
    if (resource instanceof Directory) {
      builder.append("Package participates in a cycle group");
    } else {
      builder.append("File participates in a cycle group");
    }
    boolean first = true;
    for (Resource next : tempInvolvedResources) {
      if (first) {
        if (resource instanceof Directory) {
          builder.append(" with package(s): ").append(next.getName());
        } else {
          builder.append(" with source file(s): ").append(next.getLongName());
        }
        first = false;
      } else {
        if (resource instanceof Directory) {
          builder.append(", ").append(next.getName());
        } else {
          builder.append(", ").append(next.getLongName());
        }
      }
    }
    issueBuilder.message(builder.toString());
    // An issue is attached to each of the packages involved in the cycle group.
    // The number of cycle group warnings in the issues drill-down can therefore differ
    // from the number given in Sonargraph Architect dashbox in the components dashboard.
    issuable.addIssue(issueBuilder.build());
    return true;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy