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}