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.highlighting.internal;
021
022import com.google.common.base.Preconditions;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.Iterator;
027import java.util.List;
028import org.sonar.api.batch.fs.InputFile;
029import org.sonar.api.batch.fs.TextRange;
030import org.sonar.api.batch.fs.internal.DefaultInputFile;
031import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
032import org.sonar.api.batch.sensor.highlighting.TypeOfText;
033import org.sonar.api.batch.sensor.internal.DefaultStorable;
034import org.sonar.api.batch.sensor.internal.SensorStorage;
035
036public class DefaultHighlighting extends DefaultStorable implements NewHighlighting {
037
038  private final List<SyntaxHighlightingRule> syntaxHighlightingRules;
039  private DefaultInputFile inputFile;
040
041  public DefaultHighlighting(SensorStorage storage) {
042    super(storage);
043    syntaxHighlightingRules = new ArrayList<>();
044  }
045
046  public List<SyntaxHighlightingRule> getSyntaxHighlightingRuleSet() {
047    return syntaxHighlightingRules;
048  }
049
050  private void checkOverlappingBoudaries() {
051    if (syntaxHighlightingRules.size() > 1) {
052      Iterator<SyntaxHighlightingRule> it = syntaxHighlightingRules.iterator();
053      SyntaxHighlightingRule previous = it.next();
054      while (it.hasNext()) {
055        SyntaxHighlightingRule current = it.next();
056        if (previous.range().end().compareTo(current.range().start()) > 0 && (previous.range().end().compareTo(current.range().end()) < 0)) {
057          String errorMsg = String.format("Cannot register highlighting rule for characters at %s as it " +
058            "overlaps at least one existing rule", current.range());
059          throw new IllegalStateException(errorMsg);
060        }
061        previous = current;
062      }
063    }
064  }
065
066  @Override
067  public DefaultHighlighting onFile(InputFile inputFile) {
068    Preconditions.checkNotNull(inputFile, "file can't be null");
069    this.inputFile = (DefaultInputFile) inputFile;
070    return this;
071  }
072
073  public InputFile inputFile() {
074    return inputFile;
075  }
076
077  @Override
078  public DefaultHighlighting highlight(int startOffset, int endOffset, TypeOfText typeOfText) {
079    checkInputFileNotNull();
080    TextRange newRange;
081    try {
082      // TODO remove when SONAR-7664 is implemented
083      Preconditions.checkArgument(startOffset < endOffset, "start offset should be strictly before end offset");
084      newRange = inputFile.newRange(startOffset, endOffset);
085    } catch (Exception e) {
086      throw new IllegalArgumentException("Unable to highlight file " + inputFile, e);
087    }
088    return highlight(newRange, typeOfText);
089  }
090
091  @Override
092  public DefaultHighlighting highlight(int startLine, int startLineOffset, int endLine, int endLineOffset, TypeOfText typeOfText) {
093    checkInputFileNotNull();
094    TextRange newRange;
095    try {
096      newRange = inputFile.newRange(startLine, startLineOffset, endLine, endLineOffset);
097    } catch (Exception e) {
098      throw new IllegalArgumentException("Unable to highlight file " + inputFile, e);
099    }
100    return highlight(newRange, typeOfText);
101  }
102
103  @Override
104  public DefaultHighlighting highlight(TextRange range, TypeOfText typeOfText) {
105    SyntaxHighlightingRule syntaxHighlightingRule = SyntaxHighlightingRule.create(range, typeOfText);
106    this.syntaxHighlightingRules.add(syntaxHighlightingRule);
107    return this;
108  }
109
110  @Override
111  protected void doSave() {
112    checkInputFileNotNull();
113    // Sort rules to avoid variation during consecutive runs
114    Collections.sort(syntaxHighlightingRules, new Comparator<SyntaxHighlightingRule>() {
115      @Override
116      public int compare(SyntaxHighlightingRule left, SyntaxHighlightingRule right) {
117        int result = left.range().start().compareTo(right.range().start());
118        if (result == 0) {
119          result = right.range().end().compareTo(left.range().end());
120        }
121        return result;
122      }
123    });
124    checkOverlappingBoudaries();
125    storage.store(this);
126  }
127
128  private void checkInputFileNotNull() {
129    Preconditions.checkState(inputFile != null, "Call onFile() first");
130  }
131}