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