/*
* SonarQube
* Copyright (C) 2009-2017 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 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.
*
* 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 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.scanner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import java.util.Map;
import java.util.Map.Entry;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.measure.MetricFinder;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.FileLinesContext;
import org.sonar.api.utils.KeyValueFormat;
import org.sonar.api.utils.KeyValueFormat.Converter;
import org.sonar.scanner.scan.measure.MeasureCache;
import static java.util.stream.Collectors.toMap;
public class DefaultFileLinesContext implements FileLinesContext {
private final SensorContext context;
private final InputFile inputFile;
private final MetricFinder metricFinder;
private final MeasureCache measureCache;
/**
* metric key -> line -> value
*/
private final Map<String, Map<Integer, Object>> map = Maps.newHashMap();
public DefaultFileLinesContext(SensorContext context, InputFile inputFile, MetricFinder metricFinder, MeasureCache measureCache) {
this.context = context;
this.inputFile = inputFile;
this.metricFinder = metricFinder;
this.measureCache = measureCache;
}
@Override
public void setIntValue(String metricKey, int line, int value) {
Preconditions.checkNotNull(metricKey);
checkLineRange(line);
setValue(metricKey, line, value);
}
private void checkLineRange(int line) {
Preconditions.checkArgument(line > 0, "Line number should be positive for file %s.", inputFile);
Preconditions.checkArgument(line <= inputFile.lines(), "Line %s is out of range for file %s. File has %s lines.", line, inputFile, inputFile.lines());
}
@Override
public Integer getIntValue(String metricKey, int line) {
Preconditions.checkNotNull(metricKey);
checkLineRange(line);
Map<Integer, Object> 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);
}
@Override
public void setStringValue(String metricKey, int line, String value) {
Preconditions.checkNotNull(metricKey);
checkLineRange(line);
Preconditions.checkNotNull(value);
setValue(metricKey, line, value);
}
@Override
public String getStringValue(String metricKey, int line) {
Preconditions.checkNotNull(metricKey);
checkLineRange(line);
Map<Integer, Object> 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);
}
@Override
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(optimizeStorage(metricKey, lines));
context.newMeasure()
.on(inputFile)
.forMetric(metricFinder.findByKey(metricKey))
.withValue(data)
.save();
entry.setValue(ImmutableMap.copyOf(lines));
}
}
}
private static Map<Integer, Object> optimizeStorage(String metricKey, Map<Integer, Object> lines) {
// SONAR-7464 Don't store 0 because this is default value anyway
if (CoreMetrics.NCLOC_DATA_KEY.equals(metricKey) || CoreMetrics.COMMENT_LINES_DATA_KEY.equals(metricKey) || CoreMetrics.EXECUTABLE_LINES_DATA_KEY.equals(metricKey)) {
return lines.entrySet().stream()
.filter(entry -> !entry.getValue().equals(0))
.collect(toMap(Entry<Integer, Object>::getKey, Entry<Integer, Object>::getValue));
}
return lines;
}
private Map<Integer, Object> loadData(String metricKey, Converter<? extends Object> converter) {
DefaultMeasure<?> measure = measureCache.byMetric(inputFile.key(), metricKey);
String data = measure != null ? (String) measure.value() : null;
if (data != null) {
return ImmutableMap.copyOf(KeyValueFormat.parse(data, KeyValueFormat.newIntegerConverter(), converter));
}
// no such measure
return ImmutableMap.of();
}
/**
* Checks that measure was not saved.
*
* @see #loadData(String, Converter)
* @see #save()
*/
private static boolean shouldSave(Map<Integer, Object> lines) {
return !(lines instanceof ImmutableMap);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("map", map)
.toString();
}
}