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