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.fs.internal; 021 022import com.google.common.base.Preconditions; 023import java.io.BufferedInputStream; 024import java.io.File; 025import java.io.FileInputStream; 026import java.io.FileNotFoundException; 027import java.io.InputStream; 028import java.io.StringReader; 029import java.nio.charset.Charset; 030import java.nio.file.Path; 031import java.util.Arrays; 032import javax.annotation.CheckForNull; 033import javax.annotation.Nullable; 034import org.sonar.api.batch.fs.InputFile; 035import org.sonar.api.batch.fs.TextPointer; 036import org.sonar.api.batch.fs.TextRange; 037import org.sonar.api.batch.fs.internal.FileMetadata.Metadata; 038import org.sonar.api.utils.PathUtils; 039 040/** 041 * @since 4.2 042 */ 043public class DefaultInputFile extends DefaultInputComponent implements InputFile, org.sonar.api.resources.InputFile { 044 045 private final String relativePath; 046 private final String moduleKey; 047 private Path moduleBaseDir; 048 private String language; 049 private Type type = Type.MAIN; 050 private Status status; 051 private int lines = -1; 052 private Charset charset; 053 private int lastValidOffset = -1; 054 private String hash; 055 private int nonBlankLines; 056 private int[] originalLineOffsets; 057 058 public DefaultInputFile(String moduleKey, String relativePath) { 059 this.moduleKey = moduleKey; 060 this.relativePath = PathUtils.sanitize(relativePath); 061 } 062 063 @Override 064 public String relativePath() { 065 return relativePath; 066 } 067 068 @Override 069 public String absolutePath() { 070 return PathUtils.sanitize(path().toString()); 071 } 072 073 @Override 074 public File file() { 075 return path().toFile(); 076 } 077 078 @Override 079 public Path path() { 080 if (moduleBaseDir == null) { 081 throw new IllegalStateException("Can not return the java.nio.file.Path because module baseDir is not set (see method setModuleBaseDir(java.io.File))"); 082 } 083 return moduleBaseDir.resolve(relativePath); 084 } 085 086 @CheckForNull 087 @Override 088 public String language() { 089 return language; 090 } 091 092 @Override 093 public Type type() { 094 return type; 095 } 096 097 /** 098 * {@link #setStatus(org.sonar.api.batch.fs.InputFile.Status)} 099 */ 100 @Override 101 public Status status() { 102 return status; 103 } 104 105 @Override 106 public int lines() { 107 return lines; 108 } 109 110 @Override 111 public boolean isEmpty() { 112 return lastValidOffset == 0; 113 } 114 115 /** 116 * Component key. 117 */ 118 @Override 119 public String key() { 120 return new StringBuilder().append(moduleKey).append(":").append(relativePath).toString(); 121 } 122 123 public String moduleKey() { 124 return moduleKey; 125 } 126 127 public Charset charset() { 128 return charset; 129 } 130 131 /** 132 * For testing purpose. Will be automaticall set when file is added to {@link DefaultFileSystem} 133 */ 134 public DefaultInputFile setModuleBaseDir(Path moduleBaseDir) { 135 this.moduleBaseDir = moduleBaseDir.normalize(); 136 return this; 137 } 138 139 public DefaultInputFile setLanguage(@Nullable String language) { 140 this.language = language; 141 return this; 142 } 143 144 public DefaultInputFile setType(Type type) { 145 this.type = type; 146 return this; 147 } 148 149 public DefaultInputFile setStatus(Status status) { 150 this.status = status; 151 return this; 152 } 153 154 public DefaultInputFile setLines(int lines) { 155 this.lines = lines; 156 return this; 157 } 158 159 public DefaultInputFile setCharset(Charset charset) { 160 this.charset = charset; 161 return this; 162 } 163 164 public int lastValidOffset() { 165 Preconditions.checkState(lastValidOffset >= 0, "InputFile is not properly initialized. Please set 'lastValidOffset' property."); 166 return lastValidOffset; 167 } 168 169 public DefaultInputFile setLastValidOffset(int lastValidOffset) { 170 this.lastValidOffset = lastValidOffset; 171 return this; 172 } 173 174 /** 175 * Digest hash of the file. 176 */ 177 public String hash() { 178 return hash; 179 } 180 181 public int nonBlankLines() { 182 return nonBlankLines; 183 } 184 185 public int[] originalLineOffsets() { 186 Preconditions.checkState(originalLineOffsets != null, "InputFile is not properly initialized. Please set 'originalLineOffsets' property."); 187 Preconditions.checkState(originalLineOffsets.length == lines, "InputFile is not properly initialized. 'originalLineOffsets' property length should be equal to 'lines'"); 188 return originalLineOffsets; 189 } 190 191 public DefaultInputFile setHash(String hash) { 192 this.hash = hash; 193 return this; 194 } 195 196 public DefaultInputFile setNonBlankLines(int nonBlankLines) { 197 this.nonBlankLines = nonBlankLines; 198 return this; 199 } 200 201 public DefaultInputFile setOriginalLineOffsets(int[] originalLineOffsets) { 202 this.originalLineOffsets = originalLineOffsets; 203 return this; 204 } 205 206 @Override 207 public TextPointer newPointer(int line, int lineOffset) { 208 DefaultTextPointer textPointer = new DefaultTextPointer(line, lineOffset); 209 checkValid(textPointer, "pointer"); 210 return textPointer; 211 } 212 213 private void checkValid(TextPointer pointer, String owner) { 214 Preconditions.checkArgument(pointer.line() >= 1, "%s is not a valid line for a file", pointer.line()); 215 Preconditions.checkArgument(pointer.line() <= this.lines, "%s is not a valid line for %s. File %s has %s line(s)", pointer.line(), owner, this, lines); 216 Preconditions.checkArgument(pointer.lineOffset() >= 0, "%s is not a valid line offset for a file", pointer.lineOffset()); 217 int lineLength = lineLength(pointer.line()); 218 Preconditions.checkArgument(pointer.lineOffset() <= lineLength, 219 "%s is not a valid line offset for %s. File %s has %s character(s) at line %s", pointer.lineOffset(), owner, this, lineLength, pointer.line()); 220 } 221 222 private int lineLength(int line) { 223 return lastValidGlobalOffsetForLine(line) - originalLineOffsets()[line - 1]; 224 } 225 226 private int lastValidGlobalOffsetForLine(int line) { 227 return line < this.lines ? (originalLineOffsets()[line] - 1) : lastValidOffset(); 228 } 229 230 @Override 231 public TextRange newRange(TextPointer start, TextPointer end) { 232 checkValid(start, "start pointer"); 233 checkValid(end, "end pointer"); 234 return newRangeValidPointers(start, end); 235 } 236 237 @Override 238 public TextRange newRange(int startLine, int startLineOffset, int endLine, int endLineOffset) { 239 TextPointer start = newPointer(startLine, startLineOffset); 240 TextPointer end = newPointer(endLine, endLineOffset); 241 return newRangeValidPointers(start, end); 242 } 243 244 @Override 245 public TextRange selectLine(int line) { 246 TextPointer startPointer = newPointer(line, 0); 247 TextPointer endPointer = newPointer(line, lineLength(line)); 248 return newRangeValidPointers(startPointer, endPointer); 249 } 250 251 public void validate(TextRange range) { 252 checkValid(range.start(), "start pointer"); 253 checkValid(range.end(), "end pointer"); 254 } 255 256 private static TextRange newRangeValidPointers(TextPointer start, TextPointer end) { 257 Preconditions.checkArgument(start.compareTo(end) <= 0, "Start pointer %s should be before end pointer %s", start, end); 258 return new DefaultTextRange(start, end); 259 } 260 261 /** 262 * Create Range from global offsets. Used for backward compatibility with older API. 263 */ 264 public TextRange newRange(int startOffset, int endOffset) { 265 return newRangeValidPointers(newPointer(startOffset), newPointer(endOffset)); 266 } 267 268 public TextPointer newPointer(int globalOffset) { 269 Preconditions.checkArgument(globalOffset >= 0, "%s is not a valid offset for a file", globalOffset); 270 Preconditions.checkArgument(globalOffset <= lastValidOffset(), "%s is not a valid offset for file %s. Max offset is %s", globalOffset, this, lastValidOffset()); 271 int line = findLine(globalOffset); 272 int startLineOffset = originalLineOffsets()[line - 1]; 273 return new DefaultTextPointer(line, globalOffset - startLineOffset); 274 } 275 276 private int findLine(int globalOffset) { 277 return Math.abs(Arrays.binarySearch(originalLineOffsets(), globalOffset) + 1); 278 } 279 280 public DefaultInputFile initMetadata(Metadata metadata) { 281 this.setLines(metadata.lines); 282 this.setLastValidOffset(metadata.lastValidOffset); 283 this.setNonBlankLines(metadata.nonBlankLines); 284 this.setHash(metadata.hash); 285 this.setOriginalLineOffsets(metadata.originalLineOffsets); 286 return this; 287 } 288 289 /** 290 * For testing purpose 291 */ 292 public DefaultInputFile initMetadata(String content) { 293 return initMetadata(new FileMetadata().readMetadata(new StringReader(content))); 294 } 295 296 @Override 297 public boolean equals(Object o) { 298 if (this == o) { 299 return true; 300 } 301 302 // Use instanceof to support DeprecatedDefaultInputFile 303 if (!(o instanceof DefaultInputFile)) { 304 return false; 305 } 306 307 DefaultInputFile that = (DefaultInputFile) o; 308 return moduleKey.equals(that.moduleKey) && relativePath.equals(that.relativePath); 309 } 310 311 @Override 312 public int hashCode() { 313 return moduleKey.hashCode() + relativePath.hashCode() * 13; 314 } 315 316 @Override 317 public String toString() { 318 return "[moduleKey=" + moduleKey + ", relative=" + relativePath + ", basedir=" + moduleBaseDir + "]"; 319 } 320 321 @Override 322 public File getFileBaseDir() { 323 return moduleBaseDir.toFile(); 324 } 325 326 @Override 327 public File getFile() { 328 return file(); 329 } 330 331 @Override 332 public String getRelativePath() { 333 return relativePath(); 334 } 335 336 @Override 337 public InputStream getInputStream() throws FileNotFoundException { 338 return new BufferedInputStream(new FileInputStream(file())); 339 } 340 341 @Override 342 public boolean isFile() { 343 return true; 344 } 345 346}