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