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