001/*
002 * SonarQube
003 * Copyright (C) 2009-2018 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.fs.internal;
021
022import java.io.File;
023import java.io.IOException;
024import java.io.StringReader;
025import java.nio.charset.Charset;
026import java.nio.file.Files;
027import java.nio.file.LinkOption;
028import java.nio.file.Path;
029import java.nio.file.Paths;
030import javax.annotation.CheckForNull;
031import javax.annotation.Nullable;
032import org.sonar.api.batch.bootstrap.ProjectDefinition;
033import org.sonar.api.batch.fs.InputFile;
034import org.sonar.api.utils.PathUtils;
035
036/**
037 * Intended to be used in unit tests that need to create {@link InputFile}s.
038 * An InputFile is unambiguously identified by a <b>module key</b> and a <b>relative path</b>, so these parameters are mandatory.
039 * 
040 * A module base directory is only needed to construct absolute paths.
041 * 
042 * Examples of usage of the constructors:
043 * 
044 * <pre>
045 * InputFile file1 = TestInputFileBuilder.create("module1", "myfile.java").build();
046 * InputFile file2 = TestInputFileBuilder.create("", fs.baseDir(), myfile).build();
047 * </pre>
048 * 
049 * file1 will have the "module1" as both module key and module base directory.
050 * file2 has an empty string as module key, and a relative path which is the path from the filesystem base directory to myfile.
051 * 
052 * @since 6.3
053 */
054public class TestInputFileBuilder {
055  private static int batchId = 1;
056
057  private final int id;
058  private final String relativePath;
059  private final String moduleKey;
060  @CheckForNull
061  private Path projectBaseDir;
062  private Path moduleBaseDir;
063  private String language;
064  private InputFile.Type type = InputFile.Type.MAIN;
065  private InputFile.Status status;
066  private int lines = -1;
067  private Charset charset;
068  private int lastValidOffset = -1;
069  private String hash;
070  private int nonBlankLines;
071  private int[] originalLineOffsets = new int[0];
072  private boolean publish = true;
073  private String contents;
074
075  /**
076   * Create a InputFile identified by the given module key and relative path.
077   * The module key will also be used as the module's base directory. 
078   */
079  public TestInputFileBuilder(String moduleKey, String relativePath) {
080    this(moduleKey, relativePath, batchId++);
081  }
082
083  /**
084   * Create a InputFile with a given module key and module base directory.
085   * The relative path is generated comparing the file path to the module base directory. 
086   * filePath must point to a file that is within the module base directory.
087   */
088  public TestInputFileBuilder(String moduleKey, File moduleBaseDir, File filePath) {
089    String relativePath = moduleBaseDir.toPath().relativize(filePath.toPath()).toString();
090    this.moduleKey = moduleKey;
091    setModuleBaseDir(moduleBaseDir.toPath());
092    this.relativePath = PathUtils.sanitize(relativePath);
093    this.id = batchId++;
094  }
095
096  public TestInputFileBuilder(String moduleKey, String relativePath, int id) {
097    this.moduleKey = moduleKey;
098    setModuleBaseDir(Paths.get(moduleKey));
099    this.relativePath = PathUtils.sanitize(relativePath);
100    this.id = id;
101  }
102
103  public static TestInputFileBuilder create(String moduleKey, File moduleBaseDir, File filePath) {
104    return new TestInputFileBuilder(moduleKey, moduleBaseDir, filePath);
105  }
106
107  public static TestInputFileBuilder create(String moduleKey, String relativePath) {
108    return new TestInputFileBuilder(moduleKey, relativePath);
109  }
110
111  public static int nextBatchId() {
112    return batchId++;
113  }
114
115  public TestInputFileBuilder setProjectBaseDir(Path projectBaseDir) {
116    this.projectBaseDir = normalize(projectBaseDir);
117    return this;
118  }
119
120  public TestInputFileBuilder setModuleBaseDir(Path moduleBaseDir) {
121    this.moduleBaseDir = normalize(moduleBaseDir);
122    return this;
123  }
124
125  private static Path normalize(Path path) {
126    try {
127      return path.normalize().toRealPath(LinkOption.NOFOLLOW_LINKS);
128    } catch (IOException e) {
129      return path.normalize();
130    }
131  }
132
133  public TestInputFileBuilder setLanguage(@Nullable String language) {
134    this.language = language;
135    return this;
136  }
137
138  public TestInputFileBuilder setType(InputFile.Type type) {
139    this.type = type;
140    return this;
141  }
142
143  public TestInputFileBuilder setStatus(InputFile.Status status) {
144    this.status = status;
145    return this;
146  }
147
148  public TestInputFileBuilder setLines(int lines) {
149    this.lines = lines;
150    return this;
151  }
152
153  public TestInputFileBuilder setCharset(Charset charset) {
154    this.charset = charset;
155    return this;
156  }
157
158  public TestInputFileBuilder setLastValidOffset(int lastValidOffset) {
159    this.lastValidOffset = lastValidOffset;
160    return this;
161  }
162
163  public TestInputFileBuilder setHash(String hash) {
164    this.hash = hash;
165    return this;
166  }
167
168  /**
169   * Set contents of the file and calculates metadata from it.
170   * The contents will be returned by {@link InputFile#contents()} and {@link InputFile#inputStream()} and can be
171   * inconsistent with the actual physical file pointed by {@link InputFile#path()}, {@link InputFile#absolutePath()}, etc.
172   */
173  public TestInputFileBuilder setContents(String content) {
174    this.contents = content;
175    initMetadata(content);
176    return this;
177  }
178
179  public TestInputFileBuilder setNonBlankLines(int nonBlankLines) {
180    this.nonBlankLines = nonBlankLines;
181    return this;
182  }
183
184  public TestInputFileBuilder setOriginalLineOffsets(int[] originalLineOffsets) {
185    this.originalLineOffsets = originalLineOffsets;
186    return this;
187  }
188
189  public TestInputFileBuilder setPublish(boolean publish) {
190    this.publish = publish;
191    return this;
192  }
193
194  public TestInputFileBuilder setMetadata(Metadata metadata) {
195    this.setLines(metadata.lines());
196    this.setLastValidOffset(metadata.lastValidOffset());
197    this.setNonBlankLines(metadata.nonBlankLines());
198    this.setHash(metadata.hash());
199    this.setOriginalLineOffsets(metadata.originalLineOffsets());
200    return this;
201  }
202
203  public TestInputFileBuilder initMetadata(String content) {
204    return setMetadata(new FileMetadata().readMetadata(new StringReader(content)));
205  }
206
207  public DefaultInputFile build() {
208    Path absolutePath = moduleBaseDir.resolve(relativePath);
209    if (projectBaseDir == null) {
210      projectBaseDir = moduleBaseDir;
211    }
212    String projectRelativePath = projectBaseDir.relativize(absolutePath).toString();
213    DefaultIndexedFile indexedFile = new DefaultIndexedFile(absolutePath, moduleKey, projectRelativePath, relativePath, type, language, id, new SensorStrategy());
214    DefaultInputFile inputFile = new DefaultInputFile(indexedFile,
215      f -> f.setMetadata(new Metadata(lines, nonBlankLines, hash, originalLineOffsets, lastValidOffset)),
216      contents);
217    inputFile.setStatus(status);
218    inputFile.setCharset(charset);
219    inputFile.setPublished(publish);
220    return inputFile;
221  }
222
223  public static DefaultInputModule newDefaultInputModule(String moduleKey, File baseDir) {
224    ProjectDefinition definition = ProjectDefinition.create()
225      .setKey(moduleKey)
226      .setBaseDir(baseDir)
227      .setWorkDir(new File(baseDir, ".sonar"));
228    return newDefaultInputModule(definition);
229  }
230
231  public static DefaultInputModule newDefaultInputModule(ProjectDefinition projectDefinition) {
232    return new DefaultInputModule(projectDefinition, TestInputFileBuilder.nextBatchId());
233  }
234
235  public static DefaultInputModule newDefaultInputModule(DefaultInputModule parent, String key) throws IOException {
236    Path basedir = parent.getBaseDir().resolve(key);
237    Files.createDirectory(basedir);
238    return newDefaultInputModule(key, basedir.toFile());
239  }
240
241  public static DefaultInputDir newDefaultInputDir(DefaultInputModule module, String relativePath) throws IOException {
242    Path basedir = module.getBaseDir().resolve(relativePath);
243    Files.createDirectory(basedir);
244    return new DefaultInputDir(module.key(), relativePath)
245      .setModuleBaseDir(module.getBaseDir());
246  }
247
248  public static DefaultInputFile newDefaultInputFile(Path projectBaseDir, DefaultInputModule module, String relativePath) {
249    return new TestInputFileBuilder(module.key(), relativePath)
250      .setStatus(InputFile.Status.SAME)
251      .setProjectBaseDir(projectBaseDir)
252      .setModuleBaseDir(module.getBaseDir())
253      .build();
254  }
255}