001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.batch;
021    
022    import com.google.common.annotations.Beta;
023    import com.google.common.base.Objects;
024    import com.google.common.base.Preconditions;
025    import com.google.common.collect.ImmutableMap;
026    import com.google.common.collect.Maps;
027    import org.sonar.api.batch.SonarIndex;
028    import org.sonar.api.measures.FileLinesContext;
029    import org.sonar.api.measures.Measure;
030    import org.sonar.api.measures.Metric;
031    import org.sonar.api.measures.PersistenceMode;
032    import org.sonar.api.resources.Resource;
033    import org.sonar.api.resources.ResourceUtils;
034    import org.sonar.api.utils.KeyValueFormat;
035    import org.sonar.api.utils.KeyValueFormat.Converter;
036    
037    import java.util.Map;
038    
039    /**
040     * @since 2.14
041     */
042    @Beta
043    public class DefaultFileLinesContext implements FileLinesContext {
044    
045      private final SonarIndex index;
046      private final Resource resource;
047    
048      /**
049       * metric key -> line -> value
050       */
051      private final Map<String, Map<Integer, Object>> map = Maps.newHashMap();
052    
053      public DefaultFileLinesContext(SonarIndex index, Resource resource) {
054        Preconditions.checkNotNull(index);
055        Preconditions.checkArgument(ResourceUtils.isFile(resource));
056        this.index = index;
057        this.resource = resource;
058      }
059    
060      public void setIntValue(String metricKey, int line, int value) {
061        Preconditions.checkNotNull(metricKey);
062        Preconditions.checkArgument(line > 0);
063    
064        setValue(metricKey, line, value);
065      }
066    
067      public Integer getIntValue(String metricKey, int line) {
068        Preconditions.checkNotNull(metricKey);
069        Preconditions.checkArgument(line > 0);
070    
071        Map lines = map.get(metricKey);
072        if (lines == null) {
073          // not in memory, so load
074          lines = loadData(metricKey, KeyValueFormat.newIntegerConverter());
075          map.put(metricKey, lines);
076        }
077        return (Integer) lines.get(line);
078      }
079    
080      public void setStringValue(String metricKey, int line, String value) {
081        Preconditions.checkNotNull(metricKey);
082        Preconditions.checkArgument(line > 0);
083        Preconditions.checkNotNull(value);
084    
085        setValue(metricKey, line, value);
086      }
087    
088      public String getStringValue(String metricKey, int line) {
089        Preconditions.checkNotNull(metricKey);
090        Preconditions.checkArgument(line > 0);
091    
092        Map lines = map.get(metricKey);
093        if (lines == null) {
094          // not in memory, so load
095          lines = loadData(metricKey, KeyValueFormat.newStringConverter());
096          map.put(metricKey, lines);
097        }
098        return (String) lines.get(line);
099      }
100    
101      private Map<Integer, Object> getOrCreateLines(String metricKey) {
102        Map<Integer, Object> lines = map.get(metricKey);
103        if (lines == null) {
104          lines = Maps.newHashMap();
105          map.put(metricKey, lines);
106        }
107        return lines;
108      }
109    
110      private void setValue(String metricKey, int line, Object value) {
111        getOrCreateLines(metricKey).put(line, value);
112      }
113    
114      public void save() {
115        for (Map.Entry<String, Map<Integer, Object>> entry : map.entrySet()) {
116          String metricKey = entry.getKey();
117          Map<Integer, Object> lines = entry.getValue();
118          if (shouldSave(lines)) {
119            String data = KeyValueFormat.format(lines);
120            Measure measure = new Measure(metricKey)
121                .setPersistenceMode(PersistenceMode.DATABASE)
122                .setData(data);
123            index.addMeasure(resource, measure);
124            entry.setValue(ImmutableMap.copyOf(lines));
125          }
126        }
127      }
128    
129      private Map loadData(String metricKey, Converter converter) {
130        // FIXME no way to load measure only by key
131        Measure measure = index.getMeasure(resource, new Metric(metricKey));
132        if (measure == null || measure.getData() == null) {
133          // no such measure
134          return ImmutableMap.of();
135        }
136        return ImmutableMap.copyOf(KeyValueFormat.parse(measure.getData(), KeyValueFormat.newIntegerConverter(), converter));
137      }
138    
139      /**
140       * Checks that measure was not saved.
141       *
142       * @see #loadData(String, Converter)
143       * @see #save()
144       */
145      private boolean shouldSave(Map<Integer, Object> lines) {
146        return !(lines instanceof ImmutableMap);
147      }
148    
149      @Override
150      public String toString() {
151        return Objects.toStringHelper(this)
152            .add("map", map)
153            .toString();
154      }
155    
156    }