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;
029
030import javax.annotation.Nullable;
031
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  private Path moduleBaseDir;
061  private String language;
062  private InputFile.Type type = InputFile.Type.MAIN;
063  private InputFile.Status status;
064  private int lines = -1;
065  private Charset charset;
066  private int lastValidOffset = -1;
067  private String hash;
068  private int nonBlankLines;
069  private int[] originalLineOffsets;
070  private boolean publish = true;
071  private String contents;
072
073  /**
074   * Create a InputFile identified by the given module key and relative path.
075   * The module key will also be used as the module's base directory. 
076   */
077  public TestInputFileBuilder(String moduleKey, String relativePath) {
078    this(moduleKey, relativePath, batchId++);
079  }
080
081  /**
082   * Create a InputFile with a given module key and module base directory. 
083   * The relative path is generated comparing the file path to the module base directory. 
084   * filePath must point to a file that is within the module base directory.
085   */
086  public TestInputFileBuilder(String moduleKey, File moduleBaseDir, File filePath) {
087    String relativePath = moduleBaseDir.toPath().relativize(filePath.toPath()).toString();
088    this.moduleKey = moduleKey;
089    setModuleBaseDir(moduleBaseDir.toPath());
090    this.relativePath = PathUtils.sanitize(relativePath);
091    this.id = batchId++;
092  }
093
094  public TestInputFileBuilder(String moduleKey, String relativePath, int id) {
095    this.moduleKey = moduleKey;
096    setModuleBaseDir(Paths.get(moduleKey));
097    this.relativePath = PathUtils.sanitize(relativePath);
098    this.id = id;
099  }
100
101  public static TestInputFileBuilder create(String moduleKey, File moduleBaseDir, File filePath) {
102    return new TestInputFileBuilder(moduleKey, moduleBaseDir, filePath);
103  }
104
105  public static TestInputFileBuilder create(String moduleKey, String relativePath) {
106    return new TestInputFileBuilder(moduleKey, relativePath);
107  }
108
109  public static int nextBatchId() {
110    return batchId++;
111  }
112
113  public TestInputFileBuilder setModuleBaseDir(Path moduleBaseDir) {
114    try {
115      this.moduleBaseDir = moduleBaseDir.normalize().toRealPath(LinkOption.NOFOLLOW_LINKS);
116    } catch (IOException e) {
117      this.moduleBaseDir = moduleBaseDir.normalize();
118    }
119    return this;
120  }
121
122  public TestInputFileBuilder setLanguage(@Nullable String language) {
123    this.language = language;
124    return this;
125  }
126
127  public TestInputFileBuilder setType(InputFile.Type type) {
128    this.type = type;
129    return this;
130  }
131
132  public TestInputFileBuilder setStatus(InputFile.Status status) {
133    this.status = status;
134    return this;
135  }
136
137  public TestInputFileBuilder setLines(int lines) {
138    this.lines = lines;
139    return this;
140  }
141
142  public TestInputFileBuilder setCharset(Charset charset) {
143    this.charset = charset;
144    return this;
145  }
146
147  public TestInputFileBuilder setLastValidOffset(int lastValidOffset) {
148    this.lastValidOffset = lastValidOffset;
149    return this;
150  }
151
152  public TestInputFileBuilder setHash(String hash) {
153    this.hash = hash;
154    return this;
155  }
156
157  /**
158   * Set contents of the file and calculates metadata from it.
159   * The contents will be returned by {@link InputFile#contents()} and {@link InputFile#inputStream()} and can be
160   * inconsistent with the actual physical file pointed by {@link InputFile#path()}, {@link InputFile#absolutePath()}, etc.
161   */
162  public TestInputFileBuilder setContents(String content) {
163    this.contents = content;
164    initMetadata(content);
165    return this;
166  }
167
168  public TestInputFileBuilder setNonBlankLines(int nonBlankLines) {
169    this.nonBlankLines = nonBlankLines;
170    return this;
171  }
172
173  public TestInputFileBuilder setOriginalLineOffsets(int[] originalLineOffsets) {
174    this.originalLineOffsets = originalLineOffsets;
175    return this;
176  }
177
178  public TestInputFileBuilder setPublish(boolean publish) {
179    this.publish = publish;
180    return this;
181  }
182
183  public TestInputFileBuilder setMetadata(Metadata metadata) {
184    this.setLines(metadata.lines());
185    this.setLastValidOffset(metadata.lastValidOffset());
186    this.setNonBlankLines(metadata.nonBlankLines());
187    this.setHash(metadata.hash());
188    this.setOriginalLineOffsets(metadata.originalLineOffsets());
189    return this;
190  }
191
192  public TestInputFileBuilder initMetadata(String content) {
193    return setMetadata(new FileMetadata().readMetadata(new StringReader(content)));
194  }
195
196  public DefaultInputFile build() {
197    DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, id);
198    indexedFile.setLanguage(language);
199    DefaultInputFile inputFile = new DefaultInputFile(indexedFile,
200      f -> f.setMetadata(new Metadata(lines, nonBlankLines, hash, originalLineOffsets, lastValidOffset)),
201      contents);
202    inputFile.setStatus(status);
203    inputFile.setCharset(charset);
204    inputFile.setPublish(publish);
205    return inputFile;
206  }
207
208  public static DefaultInputModule newDefaultInputModule(String moduleKey, File baseDir) {
209    ProjectDefinition definition = ProjectDefinition.create().setKey(moduleKey);
210    definition.setBaseDir(baseDir);
211    return new DefaultInputModule(definition, TestInputFileBuilder.nextBatchId());
212  }
213}