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 com.google.common.base.Preconditions; 023import java.io.File; 024import java.nio.charset.Charset; 025import java.nio.file.Path; 026import java.util.Arrays; 027import java.util.function.Consumer; 028 029import javax.annotation.CheckForNull; 030import org.sonar.api.batch.fs.InputFile; 031import org.sonar.api.batch.fs.TextPointer; 032import org.sonar.api.batch.fs.TextRange; 033 034/** 035 * @since 4.2 036 * To create {@link InputFile} in tests, use {@link TestInputFileBuilder}. 037 */ 038public class DefaultInputFile extends DefaultInputComponent implements InputFile { 039 private final DefaultIndexedFile indexedFile; 040 private final Consumer<DefaultInputFile> metadataGenerator; 041 private Status status; 042 private Charset charset; 043 private Metadata metadata; 044 private boolean publish; 045 046 public DefaultInputFile(DefaultIndexedFile indexedFile, Consumer<DefaultInputFile> metadataGenerator) { 047 super(indexedFile.batchId()); 048 this.indexedFile = indexedFile; 049 this.metadataGenerator = metadataGenerator; 050 this.metadata = null; 051 this.publish = false; 052 } 053 054 public void checkMetadata() { 055 if (metadata == null) { 056 metadataGenerator.accept(this); 057 } 058 } 059 060 /** 061 * @since 6.3 062 */ 063 public DefaultInputFile setPublish(boolean publish) { 064 this.publish = publish; 065 return this; 066 } 067 068 /** 069 * @since 6.3 070 */ 071 public boolean publish() { 072 return publish; 073 } 074 075 @Override 076 public String relativePath() { 077 return indexedFile.relativePath(); 078 } 079 080 @Override 081 public String absolutePath() { 082 return indexedFile.absolutePath(); 083 } 084 085 @Override 086 public File file() { 087 return indexedFile.file(); 088 } 089 090 @Override 091 public Path path() { 092 return indexedFile.path(); 093 } 094 095 @CheckForNull 096 @Override 097 public String language() { 098 return indexedFile.language(); 099 } 100 101 @Override 102 public Type type() { 103 return indexedFile.type(); 104 } 105 106 /** 107 * Component key (without branch). 108 */ 109 @Override 110 public String key() { 111 return indexedFile.key(); 112 } 113 114 public String moduleKey() { 115 return indexedFile.moduleKey(); 116 } 117 118 @Override 119 public int hashCode() { 120 return indexedFile.hashCode(); 121 } 122 123 @Override 124 public String toString() { 125 return indexedFile.toString(); 126 } 127 128 /** 129 * {@link #setStatus(org.sonar.api.batch.fs.InputFile.Status)} 130 */ 131 @Override 132 public Status status() { 133 checkMetadata(); 134 return status; 135 } 136 137 @Override 138 public int lines() { 139 checkMetadata(); 140 return metadata.lines(); 141 } 142 143 @Override 144 public boolean isEmpty() { 145 checkMetadata(); 146 return metadata.lastValidOffset() == 0; 147 } 148 149 @Override 150 public Charset charset() { 151 checkMetadata(); 152 return charset; 153 } 154 155 public int lastValidOffset() { 156 checkMetadata(); 157 Preconditions.checkState(metadata.lastValidOffset() >= 0, "InputFile is not properly initialized."); 158 return metadata.lastValidOffset(); 159 } 160 161 /** 162 * Digest hash of the file. 163 */ 164 public String hash() { 165 checkMetadata(); 166 return metadata.hash(); 167 } 168 169 public int nonBlankLines() { 170 checkMetadata(); 171 return metadata.nonBlankLines(); 172 } 173 174 public int[] originalLineOffsets() { 175 checkMetadata(); 176 Preconditions.checkState(metadata.originalLineOffsets() != null, "InputFile is not properly initialized."); 177 Preconditions.checkState(metadata.originalLineOffsets().length == metadata.lines(), 178 "InputFile is not properly initialized. 'originalLineOffsets' property length should be equal to 'lines'"); 179 return metadata.originalLineOffsets(); 180 } 181 182 @Override 183 public TextPointer newPointer(int line, int lineOffset) { 184 checkMetadata(); 185 DefaultTextPointer textPointer = new DefaultTextPointer(line, lineOffset); 186 checkValid(textPointer, "pointer"); 187 return textPointer; 188 } 189 190 @Override 191 public TextRange newRange(TextPointer start, TextPointer end) { 192 checkMetadata(); 193 checkValid(start, "start pointer"); 194 checkValid(end, "end pointer"); 195 return newRangeValidPointers(start, end, false); 196 } 197 198 @Override 199 public TextRange newRange(int startLine, int startLineOffset, int endLine, int endLineOffset) { 200 checkMetadata(); 201 TextPointer start = newPointer(startLine, startLineOffset); 202 TextPointer end = newPointer(endLine, endLineOffset); 203 return newRangeValidPointers(start, end, false); 204 } 205 206 @Override 207 public TextRange selectLine(int line) { 208 checkMetadata(); 209 TextPointer startPointer = newPointer(line, 0); 210 TextPointer endPointer = newPointer(line, lineLength(line)); 211 return newRangeValidPointers(startPointer, endPointer, true); 212 } 213 214 public void validate(TextRange range) { 215 checkMetadata(); 216 checkValid(range.start(), "start pointer"); 217 checkValid(range.end(), "end pointer"); 218 } 219 220 /** 221 * Create Range from global offsets. Used for backward compatibility with older API. 222 */ 223 public TextRange newRange(int startOffset, int endOffset) { 224 checkMetadata(); 225 return newRangeValidPointers(newPointer(startOffset), newPointer(endOffset), false); 226 } 227 228 public TextPointer newPointer(int globalOffset) { 229 checkMetadata(); 230 Preconditions.checkArgument(globalOffset >= 0, "%s is not a valid offset for a file", globalOffset); 231 Preconditions.checkArgument(globalOffset <= lastValidOffset(), "%s is not a valid offset for file %s. Max offset is %s", globalOffset, this, lastValidOffset()); 232 int line = findLine(globalOffset); 233 int startLineOffset = originalLineOffsets()[line - 1]; 234 return new DefaultTextPointer(line, globalOffset - startLineOffset); 235 } 236 237 public DefaultInputFile setStatus(Status status) { 238 this.status = status; 239 return this; 240 } 241 242 public DefaultInputFile setCharset(Charset charset) { 243 this.charset = charset; 244 return this; 245 } 246 247 private void checkValid(TextPointer pointer, String owner) { 248 Preconditions.checkArgument(pointer.line() >= 1, "%s is not a valid line for a file", pointer.line()); 249 Preconditions.checkArgument(pointer.line() <= this.metadata.lines(), "%s is not a valid line for %s. File %s has %s line(s)", pointer.line(), owner, this, metadata.lines()); 250 Preconditions.checkArgument(pointer.lineOffset() >= 0, "%s is not a valid line offset for a file", pointer.lineOffset()); 251 int lineLength = lineLength(pointer.line()); 252 Preconditions.checkArgument(pointer.lineOffset() <= lineLength, 253 "%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()); 254 } 255 256 private int lineLength(int line) { 257 return lastValidGlobalOffsetForLine(line) - originalLineOffsets()[line - 1]; 258 } 259 260 private int lastValidGlobalOffsetForLine(int line) { 261 return line < this.metadata.lines() ? (originalLineOffsets()[line] - 1) : lastValidOffset(); 262 } 263 264 private static TextRange newRangeValidPointers(TextPointer start, TextPointer end, boolean acceptEmptyRange) { 265 Preconditions.checkArgument(acceptEmptyRange ? (start.compareTo(end) <= 0) : (start.compareTo(end) < 0), 266 "Start pointer %s should be before end pointer %s", start, end); 267 return new DefaultTextRange(start, end); 268 } 269 270 private int findLine(int globalOffset) { 271 return Math.abs(Arrays.binarySearch(originalLineOffsets(), globalOffset) + 1); 272 } 273 274 public DefaultInputFile setMetadata(Metadata metadata) { 275 this.metadata = metadata; 276 return this; 277 } 278 279 @Override 280 public boolean equals(Object o) { 281 if (this == o) { 282 return true; 283 } 284 285 // Use instanceof to support DeprecatedDefaultInputFile 286 if (!(o instanceof DefaultInputFile)) { 287 return false; 288 } 289 290 DefaultInputFile that = (DefaultInputFile) o; 291 return this.moduleKey().equals(that.moduleKey()) && this.relativePath().equals(that.relativePath()); 292 } 293 294 @Override 295 public boolean isFile() { 296 return true; 297 } 298 299}