]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3209,SONAR-3210 Provide API to save and retrieve measures by line
authorEvgeny Mandrikov <mandrikov@gmail.com>
Thu, 9 Feb 2012 13:23:05 +0000 (17:23 +0400)
committerEvgeny Mandrikov <mandrikov@gmail.com>
Thu, 9 Feb 2012 14:48:53 +0000 (18:48 +0400)
+ Use this API to save LoC in Java files

plugins/sonar-squid-java-plugin/src/main/java/org/sonar/java/ast/visitor/FileLinesVisitor.java
plugins/sonar-squid-java-plugin/src/test/java/org/sonar/java/ast/visitor/FileLinesVisitorTest.java
sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/DefaultSensorContext.java
sonar-batch/src/test/java/org/sonar/batch/DefaultFileLinesContextTest.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/SensorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/measures/FileLinesContext.java [new file with mode: 0644]

index 31202f01d51bf436ada564413117899d4e57c668..3c1788cba46becb61140352a1dec21a508cb18f5 100644 (file)
@@ -22,8 +22,7 @@ package org.sonar.java.ast.visitor;
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 import org.sonar.api.batch.SquidUtils;
 import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.PersistenceMode;
+import org.sonar.api.measures.FileLinesContext;
 import org.sonar.api.resources.JavaFile;
 import org.sonar.plugins.squid.SonarAccessor;
 import org.sonar.squid.api.SourceFile;
@@ -31,7 +30,7 @@ import org.sonar.squid.measures.Metric;
 import org.sonar.squid.text.Source;
 
 /**
- * Saves information about lines directly into {@link org.sonar.api.batch.SensorContext}.
+ * Saves information about lines directly into Sonar index by using {@link FileLinesContext}.
  */
 public class FileLinesVisitor extends JavaAstVisitor {
 
@@ -57,21 +56,15 @@ public class FileLinesVisitor extends JavaAstVisitor {
 
   private void processFile() {
     SourceFile file = (SourceFile) peekSourceCode();
+    JavaFile javaFile = SquidUtils.convertJavaFileKeyFromSquidFormat(file.getKey());
+    FileLinesContext measures = sonarAccessor.getSensorContext().createFileLinesContext(javaFile);
+
     Source source = getSource();
-    StringBuilder data = new StringBuilder();
     for (int line = 1; line <= source.getNumberOfLines(); line++) {
       int linesOfCode = source.getMeasure(Metric.LINES_OF_CODE, line, line);
-      if (linesOfCode == 1) {
-        if (data.length() > 0) {
-          data.append(',');
-        }
-        data.append(line);
-      }
+      measures.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, linesOfCode);
     }
-    JavaFile javaFile = SquidUtils.convertJavaFileKeyFromSquidFormat(file.getKey());
-    Measure measure = new Measure(CoreMetrics.NCLOC_DATA, data.toString())
-        .setPersistenceMode(PersistenceMode.DATABASE);
-    sonarAccessor.getSensorContext().saveMeasure(javaFile, measure);
+    measures.save();
   }
 
 }
index 75edd60fb8408ccfd678693ecdb0e80768b8baf9..eb2d0808932886ea7e53ae605e4d250ea9926059 100644 (file)
@@ -24,8 +24,7 @@ import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.sonar.api.batch.SensorContext;
 import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.PersistenceMode;
+import org.sonar.api.measures.FileLinesContext;
 import org.sonar.api.resources.Resource;
 import org.sonar.java.ast.JavaAstScanner;
 import org.sonar.java.ast.SquidTestUtils;
@@ -41,26 +40,29 @@ public class FileLinesVisitorTest {
 
   private Squid squid;
   private SensorContext context;
+  private FileLinesContext measures;
 
   @Before
   public void setUp() {
     squid = new Squid(new JavaSquidConfiguration());
     context = mock(SensorContext.class);
+    measures = mock(FileLinesContext.class);
   }
 
   @Test
   public void analyseTestNcloc() {
+    ArgumentCaptor<Resource> resourceCaptor = ArgumentCaptor.forClass(Resource.class);
+    when(context.createFileLinesContext(resourceCaptor.capture()))
+        .thenReturn(measures);
+
     squid.register(SonarAccessor.class).setSensorContext(context);
     squid.register(JavaAstScanner.class).scanFile(SquidTestUtils.getInputFile("/metrics/ncloc/TestNcloc.java"));
 
-    ArgumentCaptor<Resource> resourceCaptor = ArgumentCaptor.forClass(Resource.class);
-    ArgumentCaptor<Measure> measureCaptor = ArgumentCaptor.forClass(Measure.class);
-    verify(context, times(1)).saveMeasure(resourceCaptor.capture(), measureCaptor.capture());
     assertThat(resourceCaptor.getValue().getKey(), is("[default].TestNcloc"));
-    Measure measure = measureCaptor.getValue();
-    assertThat(measure.getMetricKey(), is(CoreMetrics.NCLOC_DATA_KEY));
-    assertThat(measure.getPersistenceMode(), is(PersistenceMode.DATABASE));
-    assertThat(measure.getData(), is("1,3,4,5,6,7,8,13,14,15,16,17,19,20,21,22,23,24,25,26,27,28,29,30,32,39"));
+    verify(measures).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 1, 1);
+    verify(measures).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 2, 0);
+    verify(measures).setIntValue(CoreMetrics.NCLOC_DATA_KEY, 3, 1);
+    verify(measures).save();
   }
 
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java b/sonar-batch/src/main/java/org/sonar/batch/DefaultFileLinesContext.java
new file mode 100644 (file)
index 0000000..c4a8e9a
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.batch;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.measures.FileLinesContext;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.PersistenceMode;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.api.utils.KeyValueFormat.Converter;
+
+import java.util.Map;
+
+/**
+ * @since 2.14
+ */
+@Beta
+public class DefaultFileLinesContext implements FileLinesContext {
+
+  private final SonarIndex index;
+  private final Resource resource;
+
+  /**
+   * metric key -> line -> value
+   */
+  private final Map<String, Map<Integer, Object>> map = Maps.newHashMap();
+
+  public DefaultFileLinesContext(SonarIndex index, Resource resource) {
+    this.index = index;
+    this.resource = resource;
+  }
+
+  public void setIntValue(String metricKey, int line, int value) {
+    Preconditions.checkNotNull(metricKey);
+    Preconditions.checkArgument(line > 0);
+
+    setValue(metricKey, line, value);
+  }
+
+  public Integer getIntValue(String metricKey, int line) {
+    Preconditions.checkNotNull(metricKey);
+    Preconditions.checkArgument(line > 0);
+
+    Map lines = map.get(metricKey);
+    if (lines == null) {
+      // not in memory, so load
+      lines = loadData(metricKey, KeyValueFormat.newIntegerConverter());
+      map.put(metricKey, lines);
+    }
+    return (Integer) lines.get(line);
+  }
+
+  public void setStringValue(String metricKey, int line, String value) {
+    Preconditions.checkNotNull(metricKey);
+    Preconditions.checkArgument(line > 0);
+    Preconditions.checkNotNull(value);
+
+    setValue(metricKey, line, value);
+  }
+
+  public String getStringValue(String metricKey, int line) {
+    Preconditions.checkNotNull(metricKey);
+    Preconditions.checkArgument(line > 0);
+
+    Map lines = map.get(metricKey);
+    if (lines == null) {
+      // not in memory, so load
+      lines = loadData(metricKey, KeyValueFormat.newStringConverter());
+      map.put(metricKey, lines);
+    }
+    return (String) lines.get(line);
+  }
+
+  private Map<Integer, Object> getOrCreateLines(String metricKey) {
+    Map<Integer, Object> lines = map.get(metricKey);
+    if (lines == null) {
+      lines = Maps.newHashMap();
+      map.put(metricKey, lines);
+    }
+    return lines;
+  }
+
+  private void setValue(String metricKey, int line, Object value) {
+    getOrCreateLines(metricKey).put(line, value);
+  }
+
+  public void save() {
+    for (Map.Entry<String, Map<Integer, Object>> entry : map.entrySet()) {
+      String metricKey = entry.getKey();
+      Map<Integer, Object> lines = entry.getValue();
+      if (shouldSave(lines)) {
+        String data = KeyValueFormat.format(lines);
+        Measure measure = new Measure(metricKey)
+            .setPersistenceMode(PersistenceMode.DATABASE)
+            .setData(data);
+        index.addMeasure(resource, measure);
+      }
+    }
+  }
+
+  private Map loadData(String metricKey, Converter converter) {
+    // FIXME no way to load measure only by key
+    Measure measure = index.getMeasure(resource, new Metric(metricKey));
+    if (measure == null || measure.getData() == null) {
+      // no such measure
+      return ImmutableMap.of();
+    }
+    return ImmutableMap.copyOf(KeyValueFormat.parse(measure.getData(), KeyValueFormat.newIntegerConverter(), converter));
+  }
+
+  /**
+   * Checks that measure was not loaded.
+   *
+   * @see #loadData(String, Converter)
+   */
+  private boolean shouldSave(Map<Integer, Object> lines) {
+    return !(lines instanceof ImmutableMap);
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this)
+        .add("map", map)
+        .toString();
+  }
+
+}
index 1a5ecd309c623ca7063566a99f981898f2278df6..2a75b837b6df77dc7b64c6707851004d6b68191d 100644 (file)
@@ -23,10 +23,13 @@ import org.sonar.api.batch.Event;
 import org.sonar.api.batch.SensorContext;
 import org.sonar.api.batch.SonarIndex;
 import org.sonar.api.design.Dependency;
+import org.sonar.api.measures.FileLinesContext;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.Metric;
-import org.sonar.api.resources.*;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.ProjectLink;
+import org.sonar.api.resources.Resource;
 import org.sonar.api.rules.Violation;
 
 import java.util.Collection;
@@ -120,6 +123,10 @@ public class DefaultSensorContext implements SensorContext {
     return index.addMeasure(resourceOrProject(resource), measure);
   }
 
+  public FileLinesContext createFileLinesContext(Resource resource) {
+    return new DefaultFileLinesContext(index, resource);
+  }
+
   public void saveViolation(Violation violation, boolean force) {
     if (violation.getResource() == null) {
       violation.setResource(resourceOrProject(violation.getResource()));
diff --git a/sonar-batch/src/test/java/org/sonar/batch/DefaultFileLinesContextTest.java b/sonar-batch/src/test/java/org/sonar/batch/DefaultFileLinesContextTest.java
new file mode 100644 (file)
index 0000000..2cf15e2
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.batch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.sonar.api.batch.SonarIndex;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.PersistenceMode;
+import org.sonar.api.resources.Resource;
+
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class DefaultFileLinesContextTest {
+
+  private SonarIndex index;
+  private Resource resource;
+  private DefaultFileLinesContext fileLineMeasures;
+
+  @Before
+  public void setUp() {
+    index = mock(SonarIndex.class);
+    resource = mock(Resource.class);
+    fileLineMeasures = new DefaultFileLinesContext(index, resource);
+  }
+
+  @Test
+  public void shouldSave() {
+    fileLineMeasures.setIntValue("hits", 1, 2);
+    fileLineMeasures.setIntValue("hits", 3, 4);
+    fileLineMeasures.save();
+
+    ArgumentCaptor<Measure> measureCaptor = ArgumentCaptor.forClass(Measure.class);
+    verify(index).addMeasure(Mockito.eq(resource), measureCaptor.capture());
+    Measure measure = measureCaptor.getValue();
+    assertThat(measure.getMetricKey(), is("hits"));
+    assertThat(measure.getPersistenceMode(), is(PersistenceMode.DATABASE));
+    assertThat(measure.getData(), is("1=2;3=4"));
+  }
+
+  @Test
+  public void shouldSaveSeveral() {
+    fileLineMeasures.setIntValue("hits", 1, 2);
+    fileLineMeasures.setIntValue("hits", 3, 4);
+    fileLineMeasures.setStringValue("author", 1, "simon");
+    fileLineMeasures.setStringValue("author", 3, "evgeny");
+    fileLineMeasures.save();
+
+    verify(index, times(2)).addMeasure(Mockito.eq(resource), Mockito.any(Measure.class));
+  }
+
+  @Test
+  public void shouldLoadIntValues() {
+    when(index.getMeasure(Mockito.any(Resource.class), Mockito.any(Metric.class)))
+        .thenReturn(new Measure("hits").setData("1=2;3=4"));
+
+    assertThat(fileLineMeasures.getIntValue("hits", 1), is(2));
+    assertThat(fileLineMeasures.getIntValue("hits", 3), is(4));
+    assertThat("no measure on line", fileLineMeasures.getIntValue("hits", 5), nullValue());
+  }
+
+  @Test
+  public void shouldLoadStringValues() {
+    when(index.getMeasure(Mockito.any(Resource.class), Mockito.any(Metric.class)))
+        .thenReturn(new Measure("author").setData("1=simon;3=evgeny"));
+
+    assertThat(fileLineMeasures.getStringValue("author", 1), is("simon"));
+    assertThat(fileLineMeasures.getStringValue("author", 3), is("evgeny"));
+    assertThat("no measure on line", fileLineMeasures.getStringValue("author", 5), nullValue());
+  }
+
+  @Test
+  public void shouldNotSaveAfterLoad() {
+    when(index.getMeasure(Mockito.any(Resource.class), Mockito.any(Metric.class)))
+        .thenReturn(new Measure("author").setData("1=simon;3=evgeny"));
+
+    fileLineMeasures.getStringValue("author", 1);
+    fileLineMeasures.save();
+
+    verify(index, never()).addMeasure(Mockito.eq(resource), Mockito.any(Measure.class));
+  }
+
+  @Test
+  public void shouldNotFailIfNoMeasureInIndex() {
+    assertThat(fileLineMeasures.getIntValue("hits", 1), nullValue());
+    assertThat(fileLineMeasures.getStringValue("author", 1), nullValue());
+  }
+
+}
index fc9189410c24f54deb53599dfa1eabf2bdae4265..1cc5b2914038345c76b467acef329eaf047f89e8 100644 (file)
@@ -19,7 +19,9 @@
  */
 package org.sonar.api.batch;
 
+import com.google.common.annotations.Beta;
 import org.sonar.api.design.Dependency;
+import org.sonar.api.measures.FileLinesContext;
 import org.sonar.api.measures.Measure;
 import org.sonar.api.measures.MeasuresFilter;
 import org.sonar.api.measures.Metric;
@@ -150,6 +152,12 @@ public interface SensorContext {
    */
   Measure saveMeasure(Resource resource, Measure measure);
 
+  /**
+   * @since 2.14
+   */
+  @Beta
+  FileLinesContext createFileLinesContext(Resource resource);
+
   // ----------- RULE VIOLATIONS --------------
 
   /**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/FileLinesContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/FileLinesContext.java
new file mode 100644 (file)
index 0000000..f2fe4dc
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.api.measures;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * Provides access to measures for the lines of file.
+ * Examples:
+ * <ul>
+ * <li>line 1 is a line of code</li>
+ * <li>line 2 contains comment</li>
+ * <li>line 3 contains 2 branches</li>
+ * <li>author of line 4 is Simon</li>
+ * </ul>
+ * Numbering of lines starts from 1.
+ *
+ * <p>This interface is not intended to be implemented by clients.</p>
+ *
+ * @since 2.14
+ */
+@Beta
+public interface FileLinesContext {
+
+  void setIntValue(String metricKey, int line, int value);
+
+  /**
+   * @return value, or null if no such metric for given line
+   */
+  Integer getIntValue(String metricKey, int line);
+
+  void setStringValue(String metricKey, int line, String value);
+
+  /**
+   * @return value, or null if no such metric for given line
+   */
+  String getStringValue(String metricKey, int line);
+
+  void save();
+
+}