/* * Sonar Drools Plugin * Copyright (C) 2011 Jérémie Lagarde * dev@sonar.codehaus.org * * 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 org.sonar.plugins.drools; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.drools.builder.ResourceType; import org.drools.io.impl.FileSystemResource; import org.drools.lang.descr.RuleDescr; import org.drools.verifier.Verifier; import org.drools.verifier.builder.VerifierBuilder; import org.drools.verifier.builder.VerifierBuilderFactory; import org.drools.verifier.components.RuleComponent; import org.drools.verifier.data.VerifierReport; import org.drools.verifier.report.components.Severity; import org.drools.verifier.report.components.VerifierMessageBase; import org.sonar.api.batch.Sensor; import org.sonar.api.batch.SensorContext; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.resources.Language; import org.sonar.api.resources.Project; import org.sonar.api.resources.ProjectFileSystem; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.rules.Violation; import org.sonar.api.utils.SonarException; import org.sonar.plugins.drools.language.DrlKeywords; import org.sonar.plugins.drools.language.Drools; import org.sonar.plugins.drools.resources.DroolsFile; import org.sonar.plugins.drools.resources.DroolsPackage; import org.sonar.plugins.drools.rules.DroolsRuleRepository; import org.sonar.squid.measures.Metric; import org.sonar.squid.recognizer.CamelCaseDetector; import org.sonar.squid.recognizer.CodeRecognizer; import org.sonar.squid.recognizer.ContainsDetector; import org.sonar.squid.recognizer.Detector; import org.sonar.squid.recognizer.EndWithDetector; import org.sonar.squid.recognizer.KeywordsDetector; import org.sonar.squid.recognizer.LanguageFootprint; import org.sonar.squid.text.Source; /** * Drools sensor for standard file metrics. * * @author Jeremie Lagarde * @since 0.1 */ public class DroolsSensor implements Sensor { private static final double CODE_RECOGNIZER_SENSITIVITY = 0.9; private final RuleFinder ruleFinder; public DroolsSensor(RuleFinder ruleFinder) { this.ruleFinder = ruleFinder; } @Override public String toString() { return getClass().getSimpleName(); } public boolean shouldExecuteOnProject(Project project) { return project.getLanguage().equals(Drools.INSTANCE); } public void analyse(Project project, SensorContext context) { DroolsPlugin.configureSourceDir(project); Language drools = new Drools(project); ProjectFileSystem fileSystem = project.getFileSystem(); Map<String, DroolsPackage> packageMap = new HashMap<String, DroolsPackage>(); VerifierBuilder verifierBuilder = VerifierBuilderFactory.newVerifierBuilder(); for (File file : fileSystem.getSourceFiles(drools)) { Verifier verifier = verifierBuilder.newVerifier(); try { DroolsFile resource = DroolsFile.fromIOFile(file, false); Source source = analyseSourceCode(file); if (source != null) { context.saveMeasure(resource, CoreMetrics.LINES, (double) source.getMeasure(Metric.LINES)); context.saveMeasure(resource, CoreMetrics.NCLOC, (double) source.getMeasure(Metric.LINES_OF_CODE)); context.saveMeasure(resource, CoreMetrics.COMMENT_LINES, (double) source.getMeasure(Metric.COMMENT_LINES)); } context.saveMeasure(resource, CoreMetrics.FILES, 1.0); context.saveMeasure(resource, CoreMetrics.CLASSES, (double) (resource.getPackageDescr().getRules().size() + resource .getPackageDescr().getFunctions().size())); packageMap.put(resource.getParent().getKey(), resource.getParent()); verifier.addResourcesToVerify(new FileSystemResource(file), ResourceType.DRL); verifier.fireAnalysis(); saveViolations(resource, context, verifier.getResult()); } catch (Throwable e) { DroolsPlugin.LOG.error("error while verifier analyzing '" + file.getAbsolutePath() + "'", e); } finally { verifier.dispose(); } } for (DroolsPackage droolsPackage : packageMap.values()) { context.saveMeasure(droolsPackage, CoreMetrics.PACKAGES, 1.0); } } protected void saveViolations(DroolsFile resource, SensorContext context, VerifierReport report) { List<Violation> violations = new ArrayList<Violation>(); for (Severity severity : Severity.values()) { Collection<VerifierMessageBase> messages = report.getBySeverity(severity); for (VerifierMessageBase base : messages) { Rule rule = findRule(base); // ignore violations from report, if rule not activated in Sonar if (rule != null) { if (context.getResource(resource) != null) { int line = getLineNumber(resource, base); Violation violation = Violation.create(rule, resource).setLineId(line).setMessage(base.getMessage()); violations.add(violation); } } } } context.saveViolations(violations); } protected int getLineNumber(DroolsFile resource, VerifierMessageBase base) { for (RuleDescr ruleDescr : resource.getPackageDescr().getRules()) { if (base.getFaulty() instanceof RuleComponent) if (((RuleComponent) base.getFaulty()).getRuleName().equals(ruleDescr.getName())) { return ruleDescr.getLine(); } } return 1; } protected int getLineNumber(DroolsFile resource, RuleComponent component) { return 1; } protected Rule findRule(VerifierMessageBase base) { return ruleFinder.findByKey(DroolsRuleRepository.REPOSITORY_KEY, "DROOLS_" + base.getMessageType()); } protected static Source analyseSourceCode(File file) { Source result = null; try { result = new Source(new FileReader(file), new CodeRecognizer(CODE_RECOGNIZER_SENSITIVITY, new DrlLanguageFootprint())); } catch (FileNotFoundException e) { throw new SonarException("Unable to open file '" + file.getAbsolutePath() + "'", e); } catch (RuntimeException rEx) { DroolsPlugin.LOG.error("error while parsing file '" + file.getAbsolutePath() + "'", rEx); } return result; } private static class DrlLanguageFootprint implements LanguageFootprint { private static final double CAMEL_CASE_PROBABILITY = 0.5; private static final double CONDITIONAL_PROBABILITY = 0.95; private static final double DRL_KEYWORDS_PROBABILITY = 0.3; private static final double BOOLEAN_OPERATOR_PROBABILITY = 0.7; private static final double END_WITH_DETECTOR_PROBABILITY = 0.95; private final Set<Detector> detectors = new HashSet<Detector>(); public DrlLanguageFootprint() { detectors.add(new EndWithDetector(END_WITH_DETECTOR_PROBABILITY, '}', ';', '{')); detectors.add(new KeywordsDetector(BOOLEAN_OPERATOR_PROBABILITY, "||", "&&")); detectors.add(new KeywordsDetector(DRL_KEYWORDS_PROBABILITY, DrlKeywords.toArray())); detectors.add(new ContainsDetector(CONDITIONAL_PROBABILITY, "++", "for(", "if(", "while(", "catch(", "switch(", "try{", "else{")); detectors.add(new CamelCaseDetector(CAMEL_CASE_PROBABILITY)); } /** * @see org.sonar.squid.recognizer.LanguageFootprint#getDetectors() */ public Set<Detector> getDetectors() { return detectors; } } }