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