001/*
002 * SonarQube
003 * Copyright (C) 2009-2016 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * This program 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 * This program 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 License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019 */
020package org.sonar.api.batch.sensor.cpd.internal;
021
022import com.google.common.base.Preconditions;
023import com.google.common.collect.ImmutableList;
024import java.util.List;
025import org.sonar.api.CoreProperties;
026import org.sonar.api.batch.fs.InputFile;
027import org.sonar.api.batch.fs.TextRange;
028import org.sonar.api.batch.fs.internal.DefaultInputFile;
029import org.sonar.api.batch.fs.internal.PathPattern;
030import org.sonar.api.batch.sensor.cpd.NewCpdTokens;
031import org.sonar.api.batch.sensor.internal.DefaultStorable;
032import org.sonar.api.batch.sensor.internal.SensorStorage;
033import org.sonar.api.config.Settings;
034import org.sonar.duplications.internal.pmd.TokensLine;
035
036public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
037
038  private final Settings settings;
039  private final ImmutableList.Builder<TokensLine> result = ImmutableList.builder();
040  private DefaultInputFile inputFile;
041  private int startLine = Integer.MIN_VALUE;
042  private int startIndex = 0;
043  private int currentIndex = 0;
044  private StringBuilder sb = new StringBuilder();
045  private TextRange lastRange;
046  private boolean excluded;
047
048  public DefaultCpdTokens(Settings settings, SensorStorage storage) {
049    super(storage);
050    this.settings = settings;
051  }
052
053  @Override
054  public DefaultCpdTokens onFile(InputFile inputFile) {
055    Preconditions.checkNotNull(inputFile, "file can't be null");
056    this.inputFile = (DefaultInputFile) inputFile;
057    String language = inputFile.language();
058    if (language != null && isSkipped(language)) {
059      this.excluded = true;
060    } else {
061      String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS);
062      for (PathPattern cpdExclusion : PathPattern.create(cpdExclusions)) {
063        if (cpdExclusion.match(inputFile)) {
064          this.excluded = true;
065        }
066      }
067    }
068    return this;
069  }
070
071  boolean isSkipped(String language) {
072    String key = "sonar.cpd." + language + ".skip";
073    if (settings.hasKey(key)) {
074      return settings.getBoolean(key);
075    }
076    return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY);
077  }
078
079  public InputFile inputFile() {
080    return inputFile;
081  }
082
083  @Override
084  public NewCpdTokens addToken(int startLine, int startLineOffset, int endLine, int endLineOffset, String image) {
085    checkInputFileNotNull();
086    TextRange newRange;
087    try {
088      newRange = inputFile.newRange(startLine, startLineOffset, endLine, endLineOffset);
089    } catch (Exception e) {
090      throw new IllegalArgumentException("Unable to register token in file " + inputFile, e);
091    }
092    return addToken(newRange, image);
093  }
094
095  @Override
096  public DefaultCpdTokens addToken(TextRange range, String image) {
097    Preconditions.checkNotNull(range, "Range should not be null");
098    Preconditions.checkNotNull(image, "Image should not be null");
099    checkInputFileNotNull();
100    if (excluded) {
101      return this;
102    }
103    Preconditions.checkState(lastRange == null || lastRange.end().compareTo(range.start()) <= 0,
104      "Tokens of file %s should be provided in order.\nPrevious token: %s\nLast token: %s", inputFile, lastRange, range);
105
106    String value = image;
107
108    int line = range.start().line();
109    if (line != startLine) {
110      addNewTokensLine(result, startIndex, currentIndex, startLine, sb);
111      startIndex = currentIndex + 1;
112      startLine = line;
113    }
114    currentIndex++;
115    sb.append(value);
116    lastRange = range;
117
118    return this;
119  }
120
121  public List<TokensLine> getTokenLines() {
122    return result.build();
123  }
124
125  private static void addNewTokensLine(ImmutableList.Builder<TokensLine> result, int startUnit, int endUnit, int startLine, StringBuilder sb) {
126    if (sb.length() != 0) {
127      result.add(new TokensLine(startUnit, endUnit, startLine, sb.toString()));
128      sb.setLength(0);
129    }
130  }
131
132  @Override
133  protected void doSave() {
134    Preconditions.checkState(inputFile != null, "Call onFile() first");
135    if (excluded) {
136      return;
137    }
138    addNewTokensLine(result, startIndex, currentIndex, startLine, sb);
139    storage.store(this);
140  }
141
142  private void checkInputFileNotNull() {
143    Preconditions.checkState(inputFile != null, "Call onFile() first");
144  }
145}