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