Browse Source

Fix some quality flaws

tags/4.5-RC1
Julien HENRY 9 years ago
parent
commit
a9bd7ecdd2
21 changed files with 280 additions and 344 deletions
  1. 19
    16
      plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java
  2. 17
    13
      plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
  3. 1
    1
      plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
  4. 0
    36
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java
  5. 6
    3
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java
  6. 22
    18
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java
  7. 21
    18
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java
  8. 2
    3
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
  9. 2
    3
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java
  10. 2
    3
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
  11. 3
    3
      plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java
  12. 1
    5
      sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java
  13. 2
    1
      sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java
  14. 6
    105
      sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java
  15. 1
    2
      sonar-batch/src/main/java/org/sonar/batch/scan/measure/DeprecatedMetricFinder.java
  16. 150
    0
      sonar-batch/src/main/java/org/sonar/batch/scan2/CommonSensorContext.java
  17. 2
    104
      sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
  18. 1
    1
      sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java
  19. 8
    0
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java
  20. 12
    8
      sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueBuilder.java
  21. 2
    1
      sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java

+ 19
- 16
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java View File

@@ -110,22 +110,7 @@ public class DefaultCpdEngine extends CpdEngine {

// Create index
SonarDuplicationsIndex index = indexFactory.create(project, languageKey);

TokenizerBridge bridge = null;
if (mapping != null) {
bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey));
}
for (InputFile inputFile : sourceFiles) {
LOG.debug("Populating index from {}", inputFile);
String resourceEffectiveKey = ((DeprecatedDefaultInputFile) inputFile).key();
FileBlocks fileBlocks = duplicationCache.byComponent(resourceEffectiveKey);
if (fileBlocks != null) {
index.insert(inputFile, fileBlocks.blocks());
} else if (bridge != null) {
List<Block> blocks2 = bridge.chunk(resourceEffectiveKey, inputFile.file());
index.insert(inputFile, blocks2);
}
}
populateIndex(languageKey, sourceFiles, mapping, index);

// Detect
Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(languageKey));
@@ -157,6 +142,24 @@ public class DefaultCpdEngine extends CpdEngine {
}
}

private void populateIndex(String languageKey, List<InputFile> sourceFiles, CpdMapping mapping, SonarDuplicationsIndex index) {
TokenizerBridge bridge = null;
if (mapping != null) {
bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey));
}
for (InputFile inputFile : sourceFiles) {
LOG.debug("Populating index from {}", inputFile);
String resourceEffectiveKey = ((DeprecatedDefaultInputFile) inputFile).key();
FileBlocks fileBlocks = duplicationCache.byComponent(resourceEffectiveKey);
if (fileBlocks != null) {
index.insert(inputFile, fileBlocks.blocks());
} else if (bridge != null) {
List<Block> blocks2 = bridge.chunk(resourceEffectiveKey, inputFile.file());
index.insert(inputFile, blocks2);
}
}
}

@VisibleForTesting
int getBlockSize(String languageKey) {
int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");

+ 17
- 13
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java View File

@@ -198,20 +198,8 @@ public class JavaCpdEngine extends CpdEngine {
if (duplications == null || Iterables.isEmpty(duplications)) {
return;
}
// Calculate number of lines and blocks
Set<Integer> duplicatedLines = new HashSet<Integer>();
int duplicatedBlocks = 0;
for (CloneGroup clone : duplications) {
ClonePart origin = clone.getOriginPart();
for (ClonePart part : clone.getCloneParts()) {
if (part.getResourceId().equals(origin.getResourceId())) {
duplicatedBlocks++;
for (int duplicatedLine = part.getStartLine(); duplicatedLine < part.getStartLine() + part.getLines(); duplicatedLine++) {
duplicatedLines.add(duplicatedLine);
}
}
}
}
int duplicatedBlocks = computeBlockAndLineCount(duplications, duplicatedLines);
FileLinesContext linesContext = contextFactory.createFor(inputFile);
for (int i = 1; i <= inputFile.lines(); i++) {
linesContext.setIntValue(CoreMetrics.DUPLICATION_LINES_DATA_KEY, i, duplicatedLines.contains(i) ? 1 : 0);
@@ -246,4 +234,20 @@ public class JavaCpdEngine extends CpdEngine {
context.saveDuplications(inputFile, builder.build());
}

private static int computeBlockAndLineCount(Iterable<CloneGroup> duplications, Set<Integer> duplicatedLines) {
int duplicatedBlocks = 0;
for (CloneGroup clone : duplications) {
ClonePart origin = clone.getOriginPart();
for (ClonePart part : clone.getCloneParts()) {
if (part.getResourceId().equals(origin.getResourceId())) {
duplicatedBlocks++;
for (int duplicatedLine = part.getStartLine(); duplicatedLine < part.getStartLine() + part.getLines(); duplicatedLine++) {
duplicatedLines.add(duplicatedLine);
}
}
}
}
return duplicatedBlocks;
}

}

+ 1
- 1
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java View File

@@ -64,7 +64,7 @@ public class IndexFactory implements BatchComponent {
}

@VisibleForTesting
boolean verifyCrossProject(Project project, Logger logger) {
boolean verifyCrossProject(@Nullable Project project, Logger logger) {
boolean crossProject = false;

if (settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT)) {

+ 0
- 36
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java View File

@@ -1,36 +0,0 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.xoo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public interface XooConstants {

String PLUGIN_KEY = "xoo";
String PLUGIN_NAME = "Xoo";

String REPOSITORY_KEY = PLUGIN_KEY;
String REPOSITORY_NAME = PLUGIN_NAME;

String[] FILE_SUFFIXES = {"xoo"};

Logger LOG = LoggerFactory.getLogger("xoo");
}

+ 6
- 3
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java View File

@@ -21,6 +21,8 @@ package org.sonar.xoo.lang;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.measure.MetricFinder;
import org.sonar.api.batch.sensor.Sensor;
@@ -30,7 +32,6 @@ import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.MeasureBuilder;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.XooConstants;

import java.io.File;
import java.io.IOException;
@@ -42,6 +43,8 @@ import java.util.List;
*/
public class MeasureSensor implements Sensor {

private static final Logger LOG = LoggerFactory.getLogger(MeasureSensor.class);

private static final String MEASURES_EXTENSION = ".measures";

private MetricFinder metricFinder;
@@ -54,7 +57,7 @@ public class MeasureSensor implements Sensor {
File ioFile = inputFile.file();
File measureFile = new File(ioFile.getParentFile(), ioFile.getName() + MEASURES_EXTENSION);
if (measureFile.exists()) {
XooConstants.LOG.debug("Processing " + measureFile.getAbsolutePath());
LOG.debug("Processing " + measureFile.getAbsolutePath());
try {
List<String> lines = FileUtils.readLines(measureFile, context.fileSystem().encoding().name());
int lineNumber = 0;
@@ -81,7 +84,7 @@ public class MeasureSensor implements Sensor {
}
}

private Measure<?> createMeasure(SensorContext context, InputFile xooFile, String metricKey, String value) {
private Measure createMeasure(SensorContext context, InputFile xooFile, String metricKey, String value) {
org.sonar.api.batch.measure.Metric<Serializable> metric = metricFinder.findByKey(metricKey);
if (metric == null) {
throw new IllegalStateException("Unknow metric with key: " + metricKey);

+ 22
- 18
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java View File

@@ -22,6 +22,8 @@ package org.sonar.xoo.lang;
import com.google.common.base.Splitter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
@@ -30,7 +32,6 @@ import org.sonar.api.batch.sensor.symbol.Symbol;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.XooConstants;

import java.io.File;
import java.io.IOException;
@@ -42,41 +43,44 @@ import java.util.List;
*/
public class SymbolReferencesSensor implements Sensor {

private static final Logger LOG = LoggerFactory.getLogger(SymbolReferencesSensor.class);

private static final String SYMBOL_EXTENSION = ".symbol";

private void processFileSymbol(InputFile inputFile, SensorContext context) {
File ioFile = inputFile.file();
File symbolFile = new File(ioFile.getParentFile(), ioFile.getName() + SYMBOL_EXTENSION);
if (symbolFile.exists()) {
XooConstants.LOG.debug("Processing " + symbolFile.getAbsolutePath());
LOG.debug("Processing " + symbolFile.getAbsolutePath());
try {
List<String> lines = FileUtils.readLines(symbolFile, context.fileSystem().encoding().name());
int lineNumber = 0;
SymbolTableBuilder symbolTableBuilder = context.symbolTableBuilder(inputFile);
for (String line : lines) {
lineNumber++;
if (StringUtils.isBlank(line)) {
continue;
}
if (line.startsWith("#")) {
if (StringUtils.isBlank(line) || line.startsWith("#")) {
continue;
}
try {
Iterator<String> split = Splitter.on(",").split(line).iterator();
int startOffset = Integer.parseInt(split.next());
int endOffset = Integer.parseInt(split.next());
Symbol s = symbolTableBuilder.newSymbol(startOffset, endOffset);
while (split.hasNext()) {
symbolTableBuilder.newReference(s, Integer.parseInt(split.next()));
}
} catch (Exception e) {
throw new IllegalStateException("Error processing line " + lineNumber + " of file " + symbolFile.getAbsolutePath(), e);
}
processLine(symbolFile, lineNumber, symbolTableBuilder, line);
}
symbolTableBuilder.done();
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalStateException(e);
}
}
}

private void processLine(File symbolFile, int lineNumber, SymbolTableBuilder symbolTableBuilder, String line) {
try {
Iterator<String> split = Splitter.on(",").split(line).iterator();
int startOffset = Integer.parseInt(split.next());
int endOffset = Integer.parseInt(split.next());
Symbol s = symbolTableBuilder.newSymbol(startOffset, endOffset);
while (split.hasNext()) {
symbolTableBuilder.newReference(s, Integer.parseInt(split.next()));
}
} catch (Exception e) {
throw new IllegalStateException("Error processing line " + lineNumber + " of file " + symbolFile.getAbsolutePath(), e);
}
}


+ 21
- 18
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java View File

@@ -19,19 +19,19 @@
*/
package org.sonar.xoo.lang;

import org.sonar.api.batch.sensor.highlighting.TypeOfText;

import com.google.common.base.Splitter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.XooConstants;

import java.io.File;
import java.io.IOException;
@@ -43,42 +43,45 @@ import java.util.List;
*/
public class SyntaxHighlightingSensor implements Sensor {

private static final Logger LOG = LoggerFactory.getLogger(SyntaxHighlightingSensor.class);

private static final String HIGHLIGHTING_EXTENSION = ".highlighting";

private void processFileHighlighting(InputFile inputFile, SensorContext context) {
File ioFile = inputFile.file();
File highlightingFile = new File(ioFile.getParentFile(), ioFile.getName() + HIGHLIGHTING_EXTENSION);
if (highlightingFile.exists()) {
XooConstants.LOG.debug("Processing " + highlightingFile.getAbsolutePath());
LOG.debug("Processing " + highlightingFile.getAbsolutePath());
try {
List<String> lines = FileUtils.readLines(highlightingFile, context.fileSystem().encoding().name());
int lineNumber = 0;
HighlightingBuilder highlightingBuilder = context.highlightingBuilder(inputFile);
for (String line : lines) {
lineNumber++;
if (StringUtils.isBlank(line)) {
continue;
}
if (line.startsWith("#")) {
if (StringUtils.isBlank(line) || line.startsWith("#")) {
continue;
}
try {
Iterator<String> split = Splitter.on(":").split(line).iterator();
int startOffset = Integer.parseInt(split.next());
int endOffset = Integer.parseInt(split.next());
TypeOfText type = TypeOfText.forCssClass(split.next());
highlightingBuilder.highlight(startOffset, endOffset, type);
} catch (Exception e) {
throw new IllegalStateException("Error processing line " + lineNumber + " of file " + highlightingFile.getAbsolutePath(), e);
}
processLine(highlightingFile, lineNumber, highlightingBuilder, line);
}
highlightingBuilder.done();
} catch (IOException e) {
throw new RuntimeException(e);
throw new IllegalStateException(e);
}
}
}

private void processLine(File highlightingFile, int lineNumber, HighlightingBuilder highlightingBuilder, String line) {
try {
Iterator<String> split = Splitter.on(":").split(line).iterator();
int startOffset = Integer.parseInt(split.next());
int endOffset = Integer.parseInt(split.next());
TypeOfText type = TypeOfText.forCssClass(split.next());
highlightingBuilder.highlight(startOffset, endOffset, type);
} catch (Exception e) {
throw new IllegalStateException("Error processing line " + lineNumber + " of file " + highlightingFile.getAbsolutePath(), e);
}
}

@Override
public void describe(SensorDescriptor descriptor) {
descriptor

+ 2
- 3
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java View File

@@ -25,7 +25,6 @@ import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.XooConstants;

public class CreateIssueByInternalKeySensor implements Sensor {

@@ -36,7 +35,7 @@ public class CreateIssueByInternalKeySensor implements Sensor {
descriptor
.name("CreateIssueByInternalKeySensor")
.workOnLanguages(Xoo.KEY)
.createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
.createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
}

@@ -48,7 +47,7 @@ public class CreateIssueByInternalKeySensor implements Sensor {
}

private void createIssues(InputFile file, SensorContext context) {
ActiveRule rule = context.activeRules().findByInternalKey(XooConstants.REPOSITORY_KEY,
ActiveRule rule = context.activeRules().findByInternalKey(XooRulesDefinition.XOO_REPOSITORY,
context.settings().getString(INTERNAL_KEY_PROPERTY));
if (rule != null) {
context.addIssue(context.issueBuilder()

+ 2
- 3
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java View File

@@ -26,7 +26,6 @@ import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.rule.RuleKey;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.XooConstants;

public class OneIssueOnDirPerFileSensor implements Sensor {

@@ -37,7 +36,7 @@ public class OneIssueOnDirPerFileSensor implements Sensor {
descriptor
.name("One Issue On Dir Per File")
.workOnLanguages(Xoo.KEY)
.createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
.createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
}

@@ -49,7 +48,7 @@ public class OneIssueOnDirPerFileSensor implements Sensor {
}

private void createIssues(InputFile file, SensorContext context) {
RuleKey ruleKey = RuleKey.of(XooConstants.REPOSITORY_KEY, RULE_KEY);
RuleKey ruleKey = RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, RULE_KEY);
InputDir inputDir = context.fileSystem().inputDir(file.file().getParentFile());
if (inputDir != null) {
context.addIssue(context.issueBuilder()

+ 2
- 3
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java View File

@@ -29,7 +29,6 @@ import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.rule.RuleKey;
import org.sonar.xoo.Xoo;
import org.sonar.xoo.XooConstants;

public class OneIssuePerLineSensor implements Sensor {

@@ -43,7 +42,7 @@ public class OneIssuePerLineSensor implements Sensor {
.name("One Issue Per Line")
.dependsOn(CoreMetrics.LINES)
.workOnLanguages(Xoo.KEY)
.createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
.createIssuesForRuleRepositories(XooRulesDefinition.XOO_REPOSITORY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
}

@@ -55,7 +54,7 @@ public class OneIssuePerLineSensor implements Sensor {
}

private void createIssues(InputFile file, SensorContext context) {
RuleKey ruleKey = RuleKey.of(XooConstants.REPOSITORY_KEY, RULE_KEY);
RuleKey ruleKey = RuleKey.of(XooRulesDefinition.XOO_REPOSITORY, RULE_KEY);
Measure<Integer> linesMeasure = context.getMeasure(file, CoreMetrics.LINES);
if (linesMeasure == null) {
LoggerFactory.getLogger(getClass()).warn("Missing measure " + CoreMetrics.LINES_KEY + " on " + file);

+ 3
- 3
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/XooRulesDefinition.java View File

@@ -42,13 +42,13 @@ public class XooRulesDefinition implements RulesDefinition {
.setName("No empty line")
.setMarkdownDescription("Generate an issue on *empty* lines of Xoo source files")

// optional tags
// optional tags
.setTags("style", "security")

// optional status. Default value is READY.
// optional status. Default value is READY.
.setStatus(RuleStatus.BETA)

// default severity when the rule is activated on a Quality profile. Default value is MAJOR.
// default severity when the rule is activated on a Quality profile. Default value is MAJOR.
.setSeverity(Severity.MINOR);

// debt-related information

+ 1
- 5
sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java View File

@@ -21,8 +21,6 @@ package org.sonar.batch.duplication;

import com.google.common.base.Preconditions;
import net.sourceforge.pmd.cpd.TokenEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
@@ -37,8 +35,6 @@ import java.util.List;

public class DefaultTokenBuilder implements DuplicationTokenBuilder {

private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenBuilder.class);

private final BlockCache cache;
private final InputFile inputFile;
private final List<TokenEntry> tokens = new ArrayList<TokenEntry>();
@@ -69,7 +65,7 @@ public class DefaultTokenBuilder implements DuplicationTokenBuilder {
tokens.add(TokenEntry.getEOF());
TokenEntry.clearImages();
List<TokensLine> tokensLines = TokenizerBridge.convert(tokens);
ArrayList<Block> blocks = blockChunker.chunk(((DefaultInputFile) inputFile).key(), tokensLines);
List<Block> blocks = blockChunker.chunk(((DefaultInputFile) inputFile).key(), tokensLines);

cache.put(((DefaultInputFile) inputFile).key(), new FileBlocks(((DefaultInputFile) inputFile).key(), blocks));
tokens.clear();

+ 2
- 1
sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java View File

@@ -26,6 +26,7 @@ import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
import org.sonar.api.batch.sensor.duplication.DuplicationGroup.Block;

import java.util.ArrayList;
import java.util.List;

class DuplicationGroupValueCoder implements ValueCoder {

@@ -45,7 +46,7 @@ class DuplicationGroupValueCoder implements ValueCoder {
public Object get(Value value, Class clazz, CoderContext context) {
DuplicationGroup g = new DuplicationGroup((Block) blockCoder.get(value, DuplicationGroup.Block.class, context));
int count = value.getInt();
ArrayList<DuplicationGroup.Block> blocks = new ArrayList<DuplicationGroup.Block>(count);
List<DuplicationGroup.Block> blocks = new ArrayList<DuplicationGroup.Block>(count);
for (int i = 0; i < count; i++) {
blocks.add((Block) blockCoder.get(value, DuplicationGroup.Block.class, context));
}

+ 6
- 105
sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java View File

@@ -19,27 +19,16 @@
*/
package org.sonar.batch.scan;

import com.google.common.base.Preconditions;
import org.sonar.api.batch.Sensor;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueBuilder;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssueBuilder;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.MeasureBuilder;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.config.Settings;
import org.sonar.api.issue.Issuable;
@@ -55,66 +44,32 @@ import org.sonar.api.resources.Resource;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.RuleKey;
import org.sonar.batch.duplication.BlockCache;
import org.sonar.batch.duplication.DefaultTokenBuilder;
import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.highlighting.DefaultHighlightingBuilder;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.symbol.DefaultSymbolTableBuilder;
import org.sonar.duplications.internal.pmd.PmdBlockChunker;
import org.sonar.batch.scan2.CommonSensorContext;

import java.io.Serializable;
import java.util.List;

/**
* Implements {@link SensorContext} but forward everything to {@link org.sonar.api.batch.SensorContext} for backward compatibility.
* Will be dropped once old {@link Sensor} API is dropped.
*
*/
public class SensorContextAdaptor implements SensorContext {
public class SensorContextAdaptor extends CommonSensorContext {

private final org.sonar.api.batch.SensorContext sensorContext;
private final MetricFinder metricFinder;
private final Project project;
private final ResourcePerspectives perspectives;
private final Settings settings;
private final FileSystem fs;
private final ActiveRules activeRules;
private final ComponentDataCache componentDataCache;
private final BlockCache blockCache;
private final DuplicationCache duplicationCache;

public SensorContextAdaptor(org.sonar.api.batch.SensorContext sensorContext, MetricFinder metricFinder, Project project, ResourcePerspectives perspectives,
Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache, BlockCache blockCache,
DuplicationCache duplicationCache) {
super(settings, fs, activeRules, componentDataCache, blockCache, duplicationCache);
this.sensorContext = sensorContext;
this.metricFinder = metricFinder;
this.project = project;
this.perspectives = perspectives;
this.settings = settings;
this.fs = fs;
this.activeRules = activeRules;
this.componentDataCache = componentDataCache;
this.blockCache = blockCache;
this.duplicationCache = duplicationCache;
}

@Override
public Settings settings() {
return settings;
}

@Override
public FileSystem fileSystem() {
return fs;
}

@Override
public ActiveRules activeRules() {
return activeRules;
}

@Override
public <G extends Serializable> MeasureBuilder<G> measureBuilder() {
return new DefaultMeasureBuilder<G>();
}

@Override
@@ -143,7 +98,7 @@ public class SensorContextAdaptor implements SensorContext {
return getMeasure(file, m);
}

private Metric<?> findMetricOrFail(String metricKey) {
private Metric findMetricOrFail(String metricKey) {
Metric<?> m = metricFinder.findByKey(metricKey);
if (m == null) {
throw new IllegalStateException("Unknow metric with key: " + metricKey);
@@ -222,11 +177,6 @@ public class SensorContextAdaptor implements SensorContext {
}
}

@Override
public IssueBuilder issueBuilder() {
return new DefaultIssueBuilder();
}

@Override
public boolean addIssue(Issue issue) {
Resource r;
@@ -259,53 +209,4 @@ public class SensorContextAdaptor implements SensorContext {
.build();
}

@Override
public HighlightingBuilder highlightingBuilder(InputFile inputFile) {
return new DefaultHighlightingBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}

@Override
public SymbolTableBuilder symbolTableBuilder(InputFile inputFile) {
return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}

@Override
public DuplicationTokenBuilder duplicationTokenBuilder(InputFile inputFile) {
PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language()));
return new DefaultTokenBuilder(inputFile, blockCache, blockChunker);
}

@Override
public DuplicationBuilder duplicationBuilder(InputFile inputFile) {
return new DefaultDuplicationBuilder(inputFile);
}

@Override
public void saveDuplications(InputFile inputFile, List<DuplicationGroup> duplications) {
Preconditions.checkState(duplications.size() > 0, "Empty duplications");
String effectiveKey = ((DefaultInputFile) inputFile).key();
for (DuplicationGroup duplicationGroup : duplications) {
Preconditions.checkState(effectiveKey.equals(duplicationGroup.originBlock().resourceKey()), "Invalid duplication group");
}
duplicationCache.put(effectiveKey, duplications);
}

private int getBlockSize(String languageKey) {
int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
if (blockSize == 0) {
blockSize = getDefaultBlockSize(languageKey);
}
return blockSize;
}

private static int getDefaultBlockSize(String languageKey) {
if ("cobol".equals(languageKey)) {
return 30;
} else if ("abap".equals(languageKey) || "natur".equals(languageKey)) {
return 20;
} else {
return 10;
}
}

}

+ 1
- 2
sonar-batch/src/main/java/org/sonar/batch/scan/measure/DeprecatedMetricFinder.java View File

@@ -37,13 +37,12 @@ public final class DeprecatedMetricFinder implements MetricFinder {

public DeprecatedMetricFinder(GlobalReferentials globalReferentials) {
for (org.sonar.batch.protocol.input.Metric metric : globalReferentials.metrics()) {
Metric hibernateMetric = new org.sonar.api.measures.Metric.Builder(metric.key(), metric.key(), ValueType.valueOf(metric.valueType()))
Metric hibernateMetric = new org.sonar.api.measures.Metric.Builder(metric.key(), metric.name(), ValueType.valueOf(metric.valueType()))
.create()
.setDirection(metric.direction())
.setQualitative(metric.isQualitative())
.setUserManaged(metric.isUserManaged())
.setDescription(metric.description())
.setName(metric.name())
.setOptimizedBestValue(metric.isOptimizedBestValue())
.setBestValue(metric.bestValue())
.setWorstValue(metric.worstValue())

+ 150
- 0
sonar-batch/src/main/java/org/sonar/batch/scan2/CommonSensorContext.java View File

@@ -0,0 +1,150 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube 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.
*
* SonarQube 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.batch.scan2;

import com.google.common.base.Preconditions;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.IssueBuilder;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssueBuilder;
import org.sonar.api.batch.sensor.measure.MeasureBuilder;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.config.Settings;
import org.sonar.batch.duplication.BlockCache;
import org.sonar.batch.duplication.DefaultTokenBuilder;
import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.highlighting.DefaultHighlightingBuilder;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.scan.SensorContextAdaptor;
import org.sonar.batch.symbol.DefaultSymbolTableBuilder;
import org.sonar.duplications.internal.pmd.PmdBlockChunker;

import java.io.Serializable;
import java.util.List;

/**
* Common bits between {@link DefaultSensorContext} and {@link SensorContextAdaptor}
* @author julien
*
*/
public abstract class CommonSensorContext implements SensorContext {

private final Settings settings;
private final FileSystem fs;
private final ActiveRules activeRules;
private final ComponentDataCache componentDataCache;
private final BlockCache blockCache;
private final DuplicationCache duplicationCache;

protected CommonSensorContext(Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache,
BlockCache blockCache, DuplicationCache duplicationCache) {
this.settings = settings;
this.fs = fs;
this.activeRules = activeRules;
this.componentDataCache = componentDataCache;
this.blockCache = blockCache;
this.duplicationCache = duplicationCache;
}

@Override
public Settings settings() {
return settings;
}

@Override
public FileSystem fileSystem() {
return fs;
}

@Override
public ActiveRules activeRules() {
return activeRules;
}

@Override
public <G extends Serializable> MeasureBuilder<G> measureBuilder() {
return new DefaultMeasureBuilder<G>();
}

@Override
public IssueBuilder issueBuilder() {
return new DefaultIssueBuilder();
}

@Override
public HighlightingBuilder highlightingBuilder(InputFile inputFile) {
return new DefaultHighlightingBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}

@Override
public SymbolTableBuilder symbolTableBuilder(InputFile inputFile) {
return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}

@Override
public DuplicationTokenBuilder duplicationTokenBuilder(InputFile inputFile) {
PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language()));

return new DefaultTokenBuilder(inputFile, blockCache, blockChunker);
}

@Override
public DuplicationBuilder duplicationBuilder(InputFile inputFile) {
return new DefaultDuplicationBuilder(inputFile);
}

@Override
public void saveDuplications(InputFile inputFile, List<DuplicationGroup> duplications) {
Preconditions.checkState(!duplications.isEmpty(), "Empty duplications");
String effectiveKey = ((DefaultInputFile) inputFile).key();
for (DuplicationGroup duplicationGroup : duplications) {
Preconditions.checkState(effectiveKey.equals(duplicationGroup.originBlock().resourceKey()), "Invalid duplication group");
}
duplicationCache.put(effectiveKey, duplications);
}

private int getBlockSize(String languageKey) {
int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
if (blockSize == 0) {
blockSize = getDefaultBlockSize(languageKey);
}
return blockSize;
}

private static int getDefaultBlockSize(String languageKey) {
if ("cobol".equals(languageKey)) {
return 30;
} else if ("abap".equals(languageKey) || "natur".equals(languageKey)) {
return 20;
} else {
return 10;
}
}

}

+ 2
- 104
sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java View File

@@ -19,93 +19,46 @@
*/
package org.sonar.batch.scan2;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import org.sonar.api.batch.bootstrap.ProjectDefinition;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.rule.internal.DefaultActiveRule;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueBuilder;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
import org.sonar.api.batch.sensor.issue.internal.DefaultIssueBuilder;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.MeasureBuilder;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.config.Settings;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.duplication.BlockCache;
import org.sonar.batch.duplication.DefaultTokenBuilder;
import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.highlighting.DefaultHighlightingBuilder;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.issue.IssueFilters;
import org.sonar.batch.scan.SensorContextAdaptor;
import org.sonar.batch.symbol.DefaultSymbolTableBuilder;
import org.sonar.core.component.ComponentKeys;
import org.sonar.duplications.internal.pmd.PmdBlockChunker;

import java.io.Serializable;
import java.util.List;

public class DefaultSensorContext implements SensorContext {
public class DefaultSensorContext extends CommonSensorContext {

private final AnalyzerMeasureCache measureCache;
private final AnalyzerIssueCache issueCache;
private final ProjectDefinition def;
private final Settings settings;
private final FileSystem fs;
private final ActiveRules activeRules;
private final IssueFilters issueFilters;
private final ComponentDataCache componentDataCache;
private final BlockCache blockCache;
private final DuplicationCache duplicationCache;

public DefaultSensorContext(ProjectDefinition def, AnalyzerMeasureCache measureCache, AnalyzerIssueCache issueCache,
Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache,
BlockCache blockCache, DuplicationCache duplicationCache) {
super(settings, fs, activeRules, componentDataCache, blockCache, duplicationCache);
this.def = def;
this.measureCache = measureCache;
this.issueCache = issueCache;
this.settings = settings;
this.fs = fs;
this.activeRules = activeRules;
this.issueFilters = issueFilters;
this.componentDataCache = componentDataCache;
this.blockCache = blockCache;
this.duplicationCache = duplicationCache;
}

@Override
public Settings settings() {
return settings;
}

@Override
public FileSystem fileSystem() {
return fs;
}

@Override
public ActiveRules activeRules() {
return activeRules;
}

@Override
public <G extends Serializable> MeasureBuilder<G> measureBuilder() {
return new DefaultMeasureBuilder<G>();
}

@Override
@@ -138,11 +91,6 @@ public class DefaultSensorContext implements SensorContext {
}
}

@Override
public IssueBuilder issueBuilder() {
return new DefaultIssueBuilder();
}

@Override
public boolean addIssue(Issue issue) {
String resourceKey;
@@ -181,54 +129,4 @@ public class DefaultSensorContext implements SensorContext {
}
}

@Override
public HighlightingBuilder highlightingBuilder(InputFile inputFile) {
return new DefaultHighlightingBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}

@Override
public SymbolTableBuilder symbolTableBuilder(InputFile inputFile) {
return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}

@Override
public DuplicationTokenBuilder duplicationTokenBuilder(InputFile inputFile) {
PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language()));

return new DefaultTokenBuilder(inputFile, blockCache, blockChunker);
}

@Override
public DuplicationBuilder duplicationBuilder(InputFile inputFile) {
return new DefaultDuplicationBuilder(inputFile);
}

@Override
public void saveDuplications(InputFile inputFile, List<DuplicationGroup> duplications) {
Preconditions.checkState(duplications.size() > 0, "Empty duplications");
String effectiveKey = ((DefaultInputFile) inputFile).key();
for (DuplicationGroup duplicationGroup : duplications) {
Preconditions.checkState(effectiveKey.equals(duplicationGroup.originBlock().resourceKey()), "Invalid duplication group");
}
duplicationCache.put(effectiveKey, duplications);
}

private int getBlockSize(String languageKey) {
int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
if (blockSize == 0) {
blockSize = getDefaultBlockSize(languageKey);
}
return blockSize;
}

private static int getDefaultBlockSize(String languageKey) {
if ("cobol".equals(languageKey)) {
return 30;
} else if ("abap".equals(languageKey) || "natur".equals(languageKey)) {
return 20;
} else {
return 10;
}
}

}

+ 1
- 1
sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java View File

@@ -51,7 +51,7 @@ public class PmdBlockChunker {
/**
* @return ArrayList as we need a serializable object
*/
public ArrayList<Block> chunk(String resourceId, List<TokensLine> fragments) {
public List<Block> chunk(String resourceId, List<TokensLine> fragments) {
List<TokensLine> filtered = Lists.newArrayList();
int i = 0;
while (i < fragments.size()) {

+ 8
- 0
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java View File

@@ -79,6 +79,14 @@ public class DuplicationGroup {
.append(length, rhs.length).isEquals();
}

@Override
public int hashCode() {
return new HashCodeBuilder(13, 43)
.append(resourceKey)
.append(startLine)
.append(length).toHashCode();
}

@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).

+ 12
- 8
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueBuilder.java View File

@@ -31,6 +31,10 @@ import javax.annotation.Nullable;

public class DefaultIssueBuilder implements IssueBuilder {

private static final String INPUT_DIR_SHOULD_BE_NON_NULL = "InputDir should be non null";
private static final String INPUT_FILE_SHOULD_BE_NON_NULL = "InputFile should be non null";
private static final String ON_FILE_OR_ON_DIR_ALREADY_CALLED = "onFile or onDir already called";
private static final String ON_PROJECT_ALREADY_CALLED = "onProject already called";
String key;
boolean onProject = false;
InputPath path;
@@ -48,26 +52,26 @@ public class DefaultIssueBuilder implements IssueBuilder {

@Override
public DefaultIssueBuilder onFile(InputFile file) {
Preconditions.checkState(!this.onProject, "onProject already called");
Preconditions.checkState(this.path == null, "onFile or onDir already called");
Preconditions.checkNotNull(file, "InputFile should be non null");
Preconditions.checkState(!this.onProject, ON_PROJECT_ALREADY_CALLED);
Preconditions.checkState(this.path == null, ON_FILE_OR_ON_DIR_ALREADY_CALLED);
Preconditions.checkNotNull(file, INPUT_FILE_SHOULD_BE_NON_NULL);
this.path = file;
return this;
}

@Override
public DefaultIssueBuilder onDir(InputDir dir) {
Preconditions.checkState(!this.onProject, "onProject already called");
Preconditions.checkState(this.path == null, "onFile or onDir already called");
Preconditions.checkNotNull(dir, "InputDir should be non null");
Preconditions.checkState(!this.onProject, ON_PROJECT_ALREADY_CALLED);
Preconditions.checkState(this.path == null, ON_FILE_OR_ON_DIR_ALREADY_CALLED);
Preconditions.checkNotNull(dir, INPUT_DIR_SHOULD_BE_NON_NULL);
this.path = dir;
return this;
}

@Override
public DefaultIssueBuilder onProject() {
Preconditions.checkState(!this.onProject, "onProject already called");
Preconditions.checkState(this.path == null, "onFile or onDir already called");
Preconditions.checkState(!this.onProject, ON_PROJECT_ALREADY_CALLED);
Preconditions.checkState(this.path == null, ON_FILE_OR_ON_DIR_ALREADY_CALLED);
this.onProject = true;
return this;
}

+ 2
- 1
sonar-plugin-api/src/main/java/org/sonar/api/measures/Metric.java View File

@@ -341,6 +341,7 @@ public class Metric<G extends Serializable> implements ServerExtension, BatchExt
/**
* @return the metric description
*/
@CheckForNull
public String getDescription() {
return description;
}
@@ -351,7 +352,7 @@ public class Metric<G extends Serializable> implements ServerExtension, BatchExt
* @param description the description
* @return this
*/
public Metric setDescription(String description) {
public Metric setDescription(@Nullable String description) {
this.description = description;
return this;
}

Loading…
Cancel
Save