001/*
002 * SonarQube
003 * Copyright (C) 2009-2017 SonarSource SA
004 * mailto:info 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 java.nio.file.Paths;
023import java.util.ArrayList;
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.PathPattern;
029import org.sonar.api.batch.sensor.cpd.NewCpdTokens;
030import org.sonar.api.batch.sensor.internal.DefaultStorable;
031import org.sonar.api.batch.sensor.internal.SensorStorage;
032import org.sonar.api.config.Configuration;
033import org.sonar.duplications.internal.pmd.TokensLine;
034
035import static com.google.common.base.Preconditions.checkState;
036import static java.util.Collections.unmodifiableList;
037import static java.util.Objects.requireNonNull;
038
039public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens {
040
041  private final Configuration config;
042  private final ArrayList<TokensLine> result = new ArrayList<>();
043  private InputFile inputFile;
044  private int startLine = Integer.MIN_VALUE;
045  private int startIndex = 0;
046  private int currentIndex = 0;
047  private StringBuilder sb = new StringBuilder();
048  private TextRange lastRange;
049  private boolean excluded;
050
051  public DefaultCpdTokens(Configuration config, SensorStorage storage) {
052    super(storage);
053    this.config = config;
054  }
055
056  @Override
057  public DefaultCpdTokens onFile(InputFile inputFile) {
058    this.inputFile = requireNonNull(inputFile, "file can't be null");
059    String[] cpdExclusions = config.getStringArray(CoreProperties.CPD_EXCLUSIONS);
060    for (PathPattern cpdExclusion : PathPattern.create(cpdExclusions)) {
061      if (cpdExclusion.match(inputFile.path(), Paths.get(inputFile.relativePath()))) {
062        this.excluded = true;
063      }
064    }
065    return this;
066  }
067
068  public InputFile inputFile() {
069    return inputFile;
070  }
071
072  @Override
073  public NewCpdTokens addToken(int startLine, int startLineOffset, int endLine, int endLineOffset, String image) {
074    checkInputFileNotNull();
075    TextRange newRange;
076    try {
077      newRange = inputFile.newRange(startLine, startLineOffset, endLine, endLineOffset);
078    } catch (Exception e) {
079      throw new IllegalArgumentException("Unable to register token in file " + inputFile, e);
080    }
081    return addToken(newRange, image);
082  }
083
084  @Override
085  public DefaultCpdTokens addToken(TextRange range, String image) {
086    requireNonNull(range, "Range should not be null");
087    requireNonNull(image, "Image should not be null");
088    checkInputFileNotNull();
089    if (excluded) {
090      return this;
091    }
092    checkState(lastRange == null || lastRange.end().compareTo(range.start()) <= 0,
093      "Tokens of file %s should be provided in order.\nPrevious token: %s\nLast token: %s", inputFile, lastRange, range);
094
095    String value = image;
096
097    int line = range.start().line();
098    if (line != startLine) {
099      addNewTokensLine(result, startIndex, currentIndex, startLine, sb);
100      startIndex = currentIndex + 1;
101      startLine = line;
102    }
103    currentIndex++;
104    sb.append(value);
105    lastRange = range;
106
107    return this;
108  }
109
110  public List<TokensLine> getTokenLines() {
111    return unmodifiableList(new ArrayList<>(result));
112  }
113
114  private static void addNewTokensLine(List<TokensLine> result, int startUnit, int endUnit, int startLine, StringBuilder sb) {
115    if (sb.length() != 0) {
116      result.add(new TokensLine(startUnit, endUnit, startLine, sb.toString()));
117      sb.setLength(0);
118    }
119  }
120
121  @Override
122  protected void doSave() {
123    checkState(inputFile != null, "Call onFile() first");
124    if (excluded) {
125      return;
126    }
127    addNewTokensLine(result, startIndex, currentIndex, startLine, sb);
128    storage.store(this);
129  }
130
131  private void checkInputFileNotNull() {
132    checkState(inputFile != null, "Call onFile() first");
133  }
134}