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.symbol.internal;
021
022import com.google.common.base.Preconditions;
023import java.util.Collection;
024import java.util.LinkedHashMap;
025import java.util.Map;
026import java.util.Set;
027import java.util.TreeSet;
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.internal.DefaultStorable;
032import org.sonar.api.batch.sensor.internal.SensorStorage;
033import org.sonar.api.batch.sensor.symbol.NewSymbol;
034import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
035
036public class DefaultSymbolTable extends DefaultStorable implements NewSymbolTable {
037
038  private final Map<TextRange, Set<TextRange>> referencesBySymbol;
039  private DefaultInputFile inputFile;
040
041  public DefaultSymbolTable(SensorStorage storage) {
042    super(storage);
043    referencesBySymbol = new LinkedHashMap<>();
044  }
045
046  public Map<TextRange, Set<TextRange>> getReferencesBySymbol() {
047    return referencesBySymbol;
048  }
049
050  @Override
051  public DefaultSymbolTable onFile(InputFile inputFile) {
052    Preconditions.checkNotNull(inputFile, "file can't be null");
053    this.inputFile = (DefaultInputFile) inputFile;
054    return this;
055  }
056
057  public InputFile inputFile() {
058    return inputFile;
059  }
060
061  @Override
062  public NewSymbol newSymbol(int startLine, int startLineOffset, int endLine, int endLineOffset) {
063    checkInputFileNotNull();
064    TextRange declarationRange;
065    try {
066      declarationRange = inputFile.newRange(startLine, startLineOffset, endLine, endLineOffset);
067    } catch (Exception e) {
068      throw new IllegalArgumentException("Unable to create symbol on file " + inputFile, e);
069    }
070    return newSymbol(declarationRange);
071  }
072
073  @Override
074  public NewSymbol newSymbol(int startOffset, int endOffset) {
075    checkInputFileNotNull();
076    TextRange declarationRange;
077    try {
078      declarationRange = inputFile.newRange(startOffset, endOffset);
079    } catch (Exception e) {
080      throw new IllegalArgumentException("Unable to create symbol on file " + inputFile, e);
081    }
082    return newSymbol(declarationRange);
083  }
084
085  @Override
086  public NewSymbol newSymbol(TextRange range) {
087    checkInputFileNotNull();
088    TreeSet<TextRange> references = new TreeSet<>((o1, o2) -> o1.start().compareTo(o2.start()));
089    referencesBySymbol.put(range, references);
090    return new DefaultSymbol(inputFile, range, references);
091  }
092
093  private static class DefaultSymbol implements NewSymbol {
094
095    private final Collection<TextRange> references;
096    private final DefaultInputFile inputFile;
097    private final TextRange declaration;
098
099    public DefaultSymbol(DefaultInputFile inputFile, TextRange declaration, Collection<TextRange> references) {
100      this.inputFile = inputFile;
101      this.declaration = declaration;
102      this.references = references;
103    }
104
105    @Override
106    public NewSymbol newReference(int startOffset, int endOffset) {
107      TextRange referenceRange;
108      try {
109        referenceRange = inputFile.newRange(startOffset, endOffset);
110      } catch (Exception e) {
111        throw new IllegalArgumentException("Unable to create symbol reference on file " + inputFile, e);
112      }
113      return newReference(referenceRange);
114    }
115
116    @Override
117    public NewSymbol newReference(int startLine, int startLineOffset, int endLine, int endLineOffset) {
118      TextRange referenceRange;
119      try {
120        referenceRange = inputFile.newRange(startLine, startLineOffset, endLine, endLineOffset);
121      } catch (Exception e) {
122        throw new IllegalArgumentException("Unable to create symbol reference on file " + inputFile, e);
123      }
124      return newReference(referenceRange);
125    }
126
127    @Override
128    public NewSymbol newReference(TextRange range) {
129      Preconditions.checkNotNull(range, "Provided range is null");
130      Preconditions.checkArgument(!declaration.overlap(range), "Overlapping symbol declaration and reference for symbol at %s", declaration);
131      references.add(range);
132      return this;
133    }
134
135  }
136
137  @Override
138  protected void doSave() {
139    checkInputFileNotNull();
140    storage.store(this);
141  }
142
143  private void checkInputFileNotNull() {
144    Preconditions.checkState(inputFile != null, "Call onFile() first");
145  }
146}