diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2017-07-10 10:38:27 +0200 |
---|---|---|
committer | Duarte Meneses <duarte.meneses@sonarsource.com> | 2017-07-11 08:51:38 +0200 |
commit | 6f107dcbe90b0fea564e1ecaa96643bfc539329a (patch) | |
tree | 45478253f6006a0f1381fb506f92852ad2f5c5df /sonar-plugin-api/src/main/java/org | |
parent | f6a0f6bb86f7d244bec0b6781020e2ea066124c6 (diff) | |
download | sonarqube-6f107dcbe90b0fea564e1ecaa96643bfc539329a.tar.gz sonarqube-6f107dcbe90b0fea564e1ecaa96643bfc539329a.zip |
SONAR-9477 Deprecate ProjectReactor and ProjectBuilder
Mark Immutable classes in the Plugin API and Scanner
Diffstat (limited to 'sonar-plugin-api/src/main/java/org')
37 files changed, 596 insertions, 293 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/SonarRuntime.java b/sonar-plugin-api/src/main/java/org/sonar/api/SonarRuntime.java index bea756e4bc2..de0cfa41cee 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/SonarRuntime.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/SonarRuntime.java @@ -19,6 +19,8 @@ */ package org.sonar.api; +import javax.annotation.concurrent.Immutable; + import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; @@ -141,6 +143,7 @@ import org.sonarsource.api.sonarlint.SonarLintSide; @ServerSide @ComputeEngineSide @SonarLintSide +@Immutable public interface SonarRuntime { /** diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectBuilder.java index eac17aefa31..7d557241ee0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectBuilder.java @@ -33,17 +33,20 @@ import org.sonar.api.batch.ScannerSide; * <li>Change project metadata like description or source directories.</li> * </ul> * + * @deprecated since 6.5. It won't be possible to manipulate the project's structure. * @since 2.9 */ @ScannerSide @InstantiationStrategy(InstantiationStrategy.PER_BATCH) @ExtensionPoint +@Deprecated public abstract class ProjectBuilder { /** * Plugins can use the implementation {@link org.sonar.api.batch.bootstrap.internal.ProjectBuilderContext} * for their unit tests. */ + @Deprecated public interface Context { ProjectReactor projectReactor(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java index 19cc04bdaaf..c3c75506131 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectDefinition.java @@ -36,6 +36,8 @@ import org.sonar.api.CoreProperties; * {@link org.sonar.api.batch.bootstrap.ProjectBuilder extension point} and must not be used * by other standard extensions. * + * Since 6.5, plugins should no longer manipulate the project's structure. + * * @since 2.9 */ public class ProjectDefinition { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java index 46ea9aba68a..8aba5c679a2 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/ProjectReactor.java @@ -26,8 +26,11 @@ import java.util.List; /** * Mutable project definitions that can be modified by {@link ProjectBuilder} extensions. + * + * @deprecated since 6.5 plugins should no longer modify the project's structure * @since 2.9 */ +@Deprecated @ScannerSide public class ProjectReactor implements ProjectKey { @@ -41,7 +44,7 @@ public class ProjectReactor implements ProjectKey { } public List<ProjectDefinition> getProjects() { - return collectProjects(root, new ArrayList<ProjectDefinition>()); + return collectProjects(root, new ArrayList<>()); } /** diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/internal/ProjectBuilderContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/internal/ProjectBuilderContext.java index 6d2024037ba..b3093aa7011 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/internal/ProjectBuilderContext.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/bootstrap/internal/ProjectBuilderContext.java @@ -27,8 +27,10 @@ import org.sonar.api.batch.bootstrap.ProjectReactor; * Context that is passed to {@link org.sonar.api.batch.bootstrap.ProjectBuilder} as parameter. * Important - plugins must use this class only for unit test needs. * + * @deprecated since 6.5 * @since 3.7 */ +@Deprecated public class ProjectBuilderContext implements ProjectBuilder.Context { private final ProjectReactor reactor; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/debt/DebtRemediationFunction.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/debt/DebtRemediationFunction.java index a0b2e24404d..cbfc1368779 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/debt/DebtRemediationFunction.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/debt/DebtRemediationFunction.java @@ -22,12 +22,14 @@ package org.sonar.api.batch.debt; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.utils.Duration; +import javax.annotation.concurrent.Immutable; /** * @since 4.3 * @deprecated since 6.5 debt model will soon be unavailable on batch side */ @Deprecated +@Immutable public class DebtRemediationFunction { public enum Type { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java index 373ba890055..c1aaf9fa36f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/InputModule.java @@ -19,6 +19,9 @@ */ package org.sonar.api.batch.fs; + +import javax.annotation.concurrent.Immutable; + import org.sonar.api.batch.sensor.SensorContext; /** @@ -26,5 +29,6 @@ import org.sonar.api.batch.sensor.SensorContext; * * @since 5.2 */ +@Immutable public interface InputModule extends InputComponent { } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultIndexedFile.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultIndexedFile.java index d526e7f57cc..b3a0dbbe9ee 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultIndexedFile.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultIndexedFile.java @@ -24,8 +24,11 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; + import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + import org.sonar.api.batch.fs.IndexedFile; import org.sonar.api.batch.fs.InputFile.Type; import org.sonar.api.utils.PathUtils; @@ -33,35 +36,34 @@ import org.sonar.api.utils.PathUtils; /** * @since 6.3 */ +@Immutable public class DefaultIndexedFile extends DefaultInputComponent implements IndexedFile { private final String relativePath; private final String moduleKey; private final Path moduleBaseDir; - private String language; + private final String language; private final Type type; + private final Path path; /** * Testing purposes only! */ - public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath) { - this(moduleKey, moduleBaseDir, relativePath, TestInputFileBuilder.nextBatchId()); + public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, @Nullable String language) { + this(moduleKey, moduleBaseDir, relativePath, language, TestInputFileBuilder.nextBatchId()); } - public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, int batchId) { - this(moduleKey, moduleBaseDir, relativePath, Type.MAIN, batchId); + public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, @Nullable String language, int batchId) { + this(moduleKey, moduleBaseDir, relativePath, Type.MAIN, language, batchId); } - public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, Type type, int batchId) { + public DefaultIndexedFile(String moduleKey, Path moduleBaseDir, String relativePath, Type type, @Nullable String language, int batchId) { super(batchId); this.moduleKey = moduleKey; this.relativePath = PathUtils.sanitize(relativePath); this.moduleBaseDir = moduleBaseDir.normalize(); this.type = type; - } - - public DefaultIndexedFile setLanguage(@Nullable String language) { this.language = language; - return this; + this.path = this.moduleBaseDir.resolve(this.relativePath); } @Override @@ -81,7 +83,7 @@ public class DefaultIndexedFile extends DefaultInputComponent implements Indexed @Override public Path path() { - return moduleBaseDir.resolve(relativePath); + return path; } @Override diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java index ff881a6f75d..b3ea8263c0d 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java @@ -47,12 +47,13 @@ public class DefaultInputFile extends DefaultInputComponent implements InputFile private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; private final DefaultIndexedFile indexedFile; + private final String contents; private final Consumer<DefaultInputFile> metadataGenerator; + private Status status; private Charset charset; private Metadata metadata; private boolean publish; - private String contents; public DefaultInputFile(DefaultIndexedFile indexedFile, Consumer<DefaultInputFile> metadataGenerator) { this(indexedFile, metadataGenerator, null); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java index ac8eced2bbd..1165e54d494 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java @@ -19,13 +19,36 @@ */ package org.sonar.api.batch.fs.internal; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; + import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.InputModule; /** * @since 5.2 */ +@Immutable public class DefaultInputModule extends DefaultInputComponent implements InputModule { + private final File baseDir; + private final File workDir; + private final String name; + private final String version; + private final String originalName; + private final String originalVersion; + private final String description; + private final String keyWithBranch; + private final String branch; + private final List<String> sources; + private final List<String> tests; + private final Map<String, String> properties; private final String moduleKey; private final ProjectDefinition definition; @@ -36,9 +59,29 @@ public class DefaultInputModule extends DefaultInputComponent implements InputMo public DefaultInputModule(String moduleKey) { this(ProjectDefinition.create().setKey(moduleKey), TestInputFileBuilder.nextBatchId()); } + + /** + * For testing only! + */ + public DefaultInputModule(ProjectDefinition definition) { + this(definition, TestInputFileBuilder.nextBatchId()); + } public DefaultInputModule(ProjectDefinition definition, int batchId) { super(batchId); + this.baseDir = definition.getBaseDir(); + this.workDir = definition.getWorkDir(); + this.name = definition.getName(); + this.originalName = definition.getOriginalName(); + this.version = definition.getVersion(); + this.originalVersion = definition.getOriginalVersion(); + this.description = definition.getDescription(); + this.keyWithBranch = definition.getKeyWithBranch(); + this.branch = definition.getBranch(); + this.sources = Collections.unmodifiableList(new ArrayList<>(definition.sources())); + this.tests = Collections.unmodifiableList(new ArrayList<>(definition.tests())); + this.properties = Collections.unmodifiableMap(new HashMap<>(definition.properties())); + this.definition = definition; this.moduleKey = definition.getKey(); } @@ -59,5 +102,59 @@ public class DefaultInputModule extends DefaultInputComponent implements InputMo public ProjectDefinition definition() { return definition; } + + public File getBaseDir() { + return baseDir; + } + + public File getWorkDir() { + return workDir; + } + + public String getKeyWithBranch() { + return keyWithBranch; + } + + @CheckForNull + public String getBranch() { + return branch; + } + + public Map<String, String> properties() { + return properties; + } + + @CheckForNull + public String getOriginalVersion() { + return originalVersion; + } + + public String getVersion() { + return version; + } + + @CheckForNull + public String getOriginalName() { + return originalName; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + /** + * @return Source files and folders. + */ + public List<String> sources() { + return sources; + } + + public List<String> tests() { + return tests; + } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/FileMetadata.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/FileMetadata.java index f444c26bd0d..4a96ce61591 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/FileMetadata.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/FileMetadata.java @@ -20,252 +20,34 @@ package org.sonar.api.batch.fs.internal; import java.io.BufferedReader; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; -import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.codec.digest.DigestUtils; -import org.sonar.api.CoreProperties; import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.fs.InputFile; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; +import org.sonar.api.batch.fs.internal.charhandler.CharHandler; +import org.sonar.api.batch.fs.internal.charhandler.FileHashComputer; +import org.sonar.api.batch.fs.internal.charhandler.LineCounter; +import org.sonar.api.batch.fs.internal.charhandler.LineHashComputer; +import org.sonar.api.batch.fs.internal.charhandler.LineOffsetCounter; /** * Computes hash of files. Ends of Lines are ignored, so files with * same content but different EOL encoding have the same hash. */ @ScannerSide +@Immutable public class FileMetadata { - - private static final Logger LOG = Loggers.get(FileMetadata.class); - private static final char LINE_FEED = '\n'; private static final char CARRIAGE_RETURN = '\r'; - public abstract static class CharHandler { - - protected void handleAll(char c) { - } - - protected void handleIgnoreEoL(char c) { - } - - protected void newLine() { - } - - protected void eof() { - } - } - - private static class LineCounter extends CharHandler { - private int lines = 1; - private int nonBlankLines = 0; - private boolean blankLine = true; - boolean alreadyLoggedInvalidCharacter = false; - private final String filePath; - private final Charset encoding; - - LineCounter(String filePath, Charset encoding) { - this.filePath = filePath; - this.encoding = encoding; - } - - @Override - protected void handleAll(char c) { - if (!alreadyLoggedInvalidCharacter && c == '\ufffd') { - LOG.warn("Invalid character encountered in file {} at line {} for encoding {}. Please fix file content or configure the encoding to be used using property '{}'.", filePath, - lines, encoding, CoreProperties.ENCODING_PROPERTY); - alreadyLoggedInvalidCharacter = true; - } - } - - @Override - protected void newLine() { - lines++; - if (!blankLine) { - nonBlankLines++; - } - blankLine = true; - } - - @Override - protected void handleIgnoreEoL(char c) { - if (!Character.isWhitespace(c)) { - blankLine = false; - } - } - - @Override - protected void eof() { - if (!blankLine) { - nonBlankLines++; - } - } - - public int lines() { - return lines; - } - - public int nonBlankLines() { - return nonBlankLines; - } - - } - - private static class FileHashComputer extends CharHandler { - private MessageDigest globalMd5Digest = DigestUtils.getMd5Digest(); - private StringBuilder sb = new StringBuilder(); - private final CharsetEncoder encoder; - private final String filePath; - - public FileHashComputer(String filePath) { - encoder = StandardCharsets.UTF_8.newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - this.filePath = filePath; - } - - @Override - protected void handleIgnoreEoL(char c) { - sb.append(c); - } - - @Override - protected void newLine() { - sb.append(LINE_FEED); - processBuffer(); - sb.setLength(0); - } - - @Override - protected void eof() { - if (sb.length() > 0) { - processBuffer(); - } - } - - private void processBuffer() { - try { - if (sb.length() > 0) { - ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb)); - globalMd5Digest.update(encoded.array(), 0, encoded.limit()); - } - } catch (CharacterCodingException e) { - throw new IllegalStateException("Error encoding line hash in file: " + filePath, e); - } - } - - @CheckForNull - public String getHash() { - return Hex.encodeHexString(globalMd5Digest.digest()); - } - } - - private static class LineHashComputer extends CharHandler { - private final MessageDigest lineMd5Digest = DigestUtils.getMd5Digest(); - private final CharsetEncoder encoder; - private final StringBuilder sb = new StringBuilder(); - private final LineHashConsumer consumer; - private final File file; - private int line = 1; - - public LineHashComputer(LineHashConsumer consumer, File f) { - this.consumer = consumer; - this.file = f; - this.encoder = StandardCharsets.UTF_8.newEncoder() - .onMalformedInput(CodingErrorAction.REPLACE) - .onUnmappableCharacter(CodingErrorAction.REPLACE); - } - - @Override - protected void handleIgnoreEoL(char c) { - if (!Character.isWhitespace(c)) { - sb.append(c); - } - } - - @Override - protected void newLine() { - processBuffer(); - sb.setLength(0); - line++; - } - - @Override - protected void eof() { - if (this.line > 0) { - processBuffer(); - } - } - - private void processBuffer() { - try { - if (sb.length() > 0) { - ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb)); - lineMd5Digest.update(encoded.array(), 0, encoded.limit()); - consumer.consume(line, lineMd5Digest.digest()); - } - } catch (CharacterCodingException e) { - throw new IllegalStateException("Error encoding line hash in file: " + file.getAbsolutePath(), e); - } - } - } - - private static class LineOffsetCounter extends CharHandler { - private long currentOriginalOffset = 0; - private IntArrayList originalLineOffsets = new IntArrayList(); - private long lastValidOffset = 0; - - public LineOffsetCounter() { - originalLineOffsets.add(0); - } - - @Override - protected void handleAll(char c) { - currentOriginalOffset++; - } - - @Override - protected void newLine() { - if (currentOriginalOffset > Integer.MAX_VALUE) { - throw new IllegalStateException("File is too big: " + currentOriginalOffset); - } - originalLineOffsets.add((int) currentOriginalOffset); - } - - @Override - protected void eof() { - lastValidOffset = currentOriginalOffset; - } - - public int[] getOriginalLineOffsets() { - return originalLineOffsets.trimAndGet(); - } - - public int getLastValidOffset() { - if (lastValidOffset > Integer.MAX_VALUE) { - throw new IllegalStateException("File is too big: " + lastValidOffset); - } - return (int) lastValidOffset; - } - - } - /** * Compute hash of a file ignoring line ends differences. * Maximum performance is needed. diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/InputModuleHierarchy.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/InputModuleHierarchy.java index 468a052f507..b258fb04dd2 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/InputModuleHierarchy.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/InputModuleHierarchy.java @@ -22,10 +22,11 @@ package org.sonar.api.batch.fs.internal; import java.util.Collection; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; import org.sonar.api.batch.fs.InputModule; -import org.sonar.api.batch.fs.internal.DefaultInputModule; +@Immutable public interface InputModuleHierarchy { DefaultInputModule root(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/Metadata.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/Metadata.java index 71d79d1007a..9323c73b062 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/Metadata.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/Metadata.java @@ -19,6 +19,11 @@ */ package org.sonar.api.batch.fs.internal; +import java.util.Arrays; + +import javax.annotation.concurrent.Immutable; + +@Immutable public class Metadata { private final int lines; private final int nonBlankLines; @@ -30,7 +35,7 @@ public class Metadata { this.lines = lines; this.nonBlankLines = nonBlankLines; this.hash = hash; - this.originalLineOffsets = originalLineOffsets; + this.originalLineOffsets = Arrays.copyOf(originalLineOffsets, originalLineOffsets.length); this.lastValidOffset = lastValidOffset; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPattern.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPattern.java index 556ce3f5de0..5ec259f0021 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPattern.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPattern.java @@ -19,11 +19,13 @@ */ package org.sonar.api.batch.fs.internal; +import javax.annotation.concurrent.ThreadSafe; + import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; -import org.sonar.api.batch.fs.IndexedFile; import org.sonar.api.utils.WildcardPattern; +@ThreadSafe public abstract class PathPattern { final WildcardPattern pattern; @@ -32,9 +34,9 @@ public abstract class PathPattern { this.pattern = WildcardPattern.create(pattern); } - public abstract boolean match(IndexedFile inputFile); + public abstract boolean match(String absolutePath, String relativePath); - public abstract boolean match(IndexedFile inputFile, boolean caseSensitiveFileExtension); + public abstract boolean match(String absolutePath, String relativePath, boolean caseSensitiveFileExtension); public static PathPattern create(String s) { String trimmed = StringUtils.trim(s); @@ -58,15 +60,15 @@ public abstract class PathPattern { } @Override - public boolean match(IndexedFile inputFile) { - return match(inputFile, true); + public boolean match(String absolutePath, String relativePath) { + return match(absolutePath, relativePath, true); } @Override - public boolean match(IndexedFile inputFile, boolean caseSensitiveFileExtension) { - String path = inputFile.absolutePath(); + public boolean match(String absolutePath, String relativePath, boolean caseSensitiveFileExtension) { + String path = absolutePath; if (!caseSensitiveFileExtension) { - String extension = sanitizeExtension(FilenameUtils.getExtension(inputFile.file().getName())); + String extension = sanitizeExtension(FilenameUtils.getExtension(relativePath)); if (StringUtils.isNotBlank(extension)) { path = StringUtils.removeEndIgnoreCase(path, extension); path = path + extension; @@ -90,15 +92,15 @@ public abstract class PathPattern { } @Override - public boolean match(IndexedFile inputFile) { - return match(inputFile, true); + public boolean match(String absolutePath, String relativePath) { + return match(absolutePath, relativePath, true); } @Override - public boolean match(IndexedFile inputFile, boolean caseSensitiveFileExtension) { - String path = inputFile.relativePath(); + public boolean match(String absolutePath, String relativePath, boolean caseSensitiveFileExtension) { + String path = relativePath; if (!caseSensitiveFileExtension) { - String extension = sanitizeExtension(FilenameUtils.getExtension(inputFile.file().getName())); + String extension = sanitizeExtension(FilenameUtils.getExtension(relativePath)); if (StringUtils.isNotBlank(extension)) { path = StringUtils.removeEndIgnoreCase(path, extension); path = path + extension; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPatternPredicate.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPatternPredicate.java index 400fd64f37b..e6b4a9246e3 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPatternPredicate.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/PathPatternPredicate.java @@ -34,7 +34,7 @@ class PathPatternPredicate extends AbstractFilePredicate { @Override public boolean apply(InputFile f) { - return pattern.match(f); + return pattern.match(f.absolutePath(), f.relativePath()); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/TestInputFileBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/TestInputFileBuilder.java index 6fc584fdfbc..fa86a9e17e6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/TestInputFileBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/TestInputFileBuilder.java @@ -66,7 +66,7 @@ public class TestInputFileBuilder { private int lastValidOffset = -1; private String hash; private int nonBlankLines; - private int[] originalLineOffsets; + private int[] originalLineOffsets = new int[0]; private boolean publish = true; private String contents; @@ -194,8 +194,7 @@ public class TestInputFileBuilder { } public DefaultInputFile build() { - DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, id); - indexedFile.setLanguage(language); + DefaultIndexedFile indexedFile = new DefaultIndexedFile(moduleKey, moduleBaseDir, relativePath, type, language, id); DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> f.setMetadata(new Metadata(lines, nonBlankLines, hash, originalLineOffsets, lastValidOffset)), contents); @@ -206,8 +205,11 @@ public class TestInputFileBuilder { } public static DefaultInputModule newDefaultInputModule(String moduleKey, File baseDir) { - ProjectDefinition definition = ProjectDefinition.create().setKey(moduleKey); - definition.setBaseDir(baseDir); - return new DefaultInputModule(definition, TestInputFileBuilder.nextBatchId()); + ProjectDefinition definition = ProjectDefinition.create().setKey(moduleKey).setBaseDir(baseDir); + return newDefaultInputModule(definition); + } + + public static DefaultInputModule newDefaultInputModule(ProjectDefinition projectDefinition) { + return new DefaultInputModule(projectDefinition, TestInputFileBuilder.nextBatchId()); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/CharHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/CharHandler.java new file mode 100644 index 00000000000..76941323906 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/CharHandler.java @@ -0,0 +1,35 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.charhandler; + +public abstract class CharHandler { + + public void handleAll(char c) { + } + + public void handleIgnoreEoL(char c) { + } + + public void newLine() { + } + + public void eof() { + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/FileHashComputer.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/FileHashComputer.java new file mode 100644 index 00000000000..d1bfa79207e --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/FileHashComputer.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.charhandler; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import javax.annotation.CheckForNull; + +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; + +public class FileHashComputer extends CharHandler { + private static final char LINE_FEED = '\n'; + + + private MessageDigest globalMd5Digest = DigestUtils.getMd5Digest(); + private StringBuilder sb = new StringBuilder(); + private final CharsetEncoder encoder; + private final String filePath; + + public FileHashComputer(String filePath) { + encoder = StandardCharsets.UTF_8.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + this.filePath = filePath; + } + + @Override + public void handleIgnoreEoL(char c) { + sb.append(c); + } + + @Override + public void newLine() { + sb.append(LINE_FEED); + processBuffer(); + sb.setLength(0); + } + + @Override + public void eof() { + if (sb.length() > 0) { + processBuffer(); + } + } + + private void processBuffer() { + try { + if (sb.length() > 0) { + ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb)); + globalMd5Digest.update(encoded.array(), 0, encoded.limit()); + } + } catch (CharacterCodingException e) { + throw new IllegalStateException("Error encoding line hash in file: " + filePath, e); + } + } + + @CheckForNull + public String getHash() { + return Hex.encodeHexString(globalMd5Digest.digest()); + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/IntArrayList.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayList.java index 913c0a2433b..2bdcfb6a852 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/IntArrayList.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/IntArrayList.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.api.batch.fs.internal; +package org.sonar.api.batch.fs.internal.charhandler; import java.util.Arrays; import java.util.Collection; @@ -72,11 +72,12 @@ class IntArrayList { } private void ensureCapacityInternal(int minCapacity) { + int capacity = minCapacity; if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + capacity = Math.max(DEFAULT_CAPACITY, minCapacity); } - ensureExplicitCapacity(minCapacity); + ensureExplicitCapacity(capacity); } private void ensureExplicitCapacity(int minCapacity) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineCounter.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineCounter.java new file mode 100644 index 00000000000..c17a867f295 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineCounter.java @@ -0,0 +1,83 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.charhandler; + +import java.nio.charset.Charset; + +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +public class LineCounter extends CharHandler { + private static final Logger LOG = Loggers.get(LineCounter.class); + + private int lines = 1; + private int nonBlankLines = 0; + private boolean blankLine = true; + boolean alreadyLoggedInvalidCharacter = false; + private final String filePath; + private final Charset encoding; + + public LineCounter(String filePath, Charset encoding) { + this.filePath = filePath; + this.encoding = encoding; + } + + @Override + public void handleAll(char c) { + if (!alreadyLoggedInvalidCharacter && c == '\ufffd') { + LOG.warn("Invalid character encountered in file {} at line {} for encoding {}. Please fix file content or configure the encoding to be used using property '{}'.", filePath, + lines, encoding, CoreProperties.ENCODING_PROPERTY); + alreadyLoggedInvalidCharacter = true; + } + } + + @Override + public void newLine() { + lines++; + if (!blankLine) { + nonBlankLines++; + } + blankLine = true; + } + + @Override + public void handleIgnoreEoL(char c) { + if (!Character.isWhitespace(c)) { + blankLine = false; + } + } + + @Override + public void eof() { + if (!blankLine) { + nonBlankLines++; + } + } + + public int lines() { + return lines; + } + + public int nonBlankLines() { + return nonBlankLines; + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineHashComputer.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineHashComputer.java new file mode 100644 index 00000000000..f371c71f5a1 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineHashComputer.java @@ -0,0 +1,82 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.charhandler; + +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetEncoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +import org.apache.commons.codec.digest.DigestUtils; +import org.sonar.api.batch.fs.internal.FileMetadata.LineHashConsumer; + +public class LineHashComputer extends CharHandler { + private final MessageDigest lineMd5Digest = DigestUtils.getMd5Digest(); + private final CharsetEncoder encoder; + private final StringBuilder sb = new StringBuilder(); + private final LineHashConsumer consumer; + private final File file; + private int line = 1; + + public LineHashComputer(LineHashConsumer consumer, File f) { + this.consumer = consumer; + this.file = f; + this.encoder = StandardCharsets.UTF_8.newEncoder() + .onMalformedInput(CodingErrorAction.REPLACE) + .onUnmappableCharacter(CodingErrorAction.REPLACE); + } + + @Override + public void handleIgnoreEoL(char c) { + if (!Character.isWhitespace(c)) { + sb.append(c); + } + } + + @Override + public void newLine() { + processBuffer(); + sb.setLength(0); + line++; + } + + @Override + public void eof() { + if (this.line > 0) { + processBuffer(); + } + } + + private void processBuffer() { + try { + if (sb.length() > 0) { + ByteBuffer encoded = encoder.encode(CharBuffer.wrap(sb)); + lineMd5Digest.update(encoded.array(), 0, encoded.limit()); + consumer.consume(line, lineMd5Digest.digest()); + } + } catch (CharacterCodingException e) { + throw new IllegalStateException("Error encoding line hash in file: " + file.getAbsolutePath(), e); + } + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineOffsetCounter.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineOffsetCounter.java new file mode 100644 index 00000000000..cf39d16267f --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/LineOffsetCounter.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.api.batch.fs.internal.charhandler; + +public class LineOffsetCounter extends CharHandler { + private long currentOriginalOffset = 0; + private IntArrayList originalLineOffsets = new IntArrayList(); + private long lastValidOffset = 0; + + public LineOffsetCounter() { + originalLineOffsets.add(0); + } + + @Override + public void handleAll(char c) { + currentOriginalOffset++; + } + + @Override + public void newLine() { + if (currentOriginalOffset > Integer.MAX_VALUE) { + throw new IllegalStateException("File is too big: " + currentOriginalOffset); + } + originalLineOffsets.add((int) currentOriginalOffset); + } + + @Override + public void eof() { + lastValidOffset = currentOriginalOffset; + } + + public int[] getOriginalLineOffsets() { + return originalLineOffsets.trimAndGet(); + } + + public int getLastValidOffset() { + if (lastValidOffset > Integer.MAX_VALUE) { + throw new IllegalStateException("File is too big: " + lastValidOffset); + } + return (int) lastValidOffset; + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/package-info.java new file mode 100644 index 00000000000..b8d3c63fcff --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/charhandler/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +@ParametersAreNonnullByDefault +package org.sonar.api.batch.fs.internal.charhandler; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/measure/MetricFinder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/measure/MetricFinder.java index 7e8dffb5903..c51c5ddf969 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/measure/MetricFinder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/measure/MetricFinder.java @@ -23,12 +23,15 @@ import java.io.Serializable; import java.util.Collection; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.ThreadSafe; + import org.sonar.api.batch.ScannerSide; /** * @since 4.5 */ @ScannerSide +@ThreadSafe public interface MetricFinder { @CheckForNull diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRule.java index bf96a7a1103..c0df514efc0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRule.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRule.java @@ -21,12 +21,15 @@ package org.sonar.api.batch.rule; import java.util.Map; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; + import org.sonar.api.rule.RuleKey; /** * Configuration of a rule activated on a Quality profile * @since 4.2 */ +@Immutable public interface ActiveRule { RuleKey ruleKey(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRules.java index 1ea2b013ed7..f25c75a5af8 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/ActiveRules.java @@ -23,6 +23,7 @@ import org.sonar.api.batch.ScannerSide; import org.sonar.api.rule.RuleKey; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; import java.util.Collection; @@ -35,6 +36,7 @@ import java.util.Collection; * * @since 4.2 */ +@Immutable @ScannerSide public interface ActiveRules { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java index 7c06501dfbe..4781bb39fdd 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/RuleParam.java @@ -19,10 +19,14 @@ */ package org.sonar.api.batch.rule; +import javax.annotation.concurrent.Immutable; + /** * @since 4.2 */ +@Immutable public interface RuleParam { String key(); + String description(); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java index 20b755c6fc7..c73cc867eee 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/Rules.java @@ -23,6 +23,7 @@ import org.sonar.api.batch.ScannerSide; import org.sonar.api.rule.RuleKey; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; import java.util.Collection; @@ -33,6 +34,7 @@ import java.util.Collection; * @since 4.2 */ @ScannerSide +@Immutable public interface Rules { /** diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRules.java index 9f4fd4bdbb0..981ebbbedde 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRules.java @@ -20,7 +20,6 @@ package org.sonar.api.batch.rule.internal; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ListMultimap; import org.sonar.api.batch.rule.ActiveRule; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.rule.RuleKey; @@ -28,35 +27,32 @@ import org.sonar.api.rule.RuleKey; import javax.annotation.concurrent.Immutable; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @Immutable public class DefaultActiveRules implements ActiveRules { - - // TODO use disk-backed cache (persistit) instead of full in-memory cache ? - private final ListMultimap<String, ActiveRule> activeRulesByRepository; + private final ImmutableListMultimap<String, ActiveRule> activeRulesByRepository; private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndKey = new HashMap<>(); private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndInternalKey = new HashMap<>(); - private final ListMultimap<String, ActiveRule> activeRulesByLanguage; + private final ImmutableListMultimap<String, ActiveRule> activeRulesByLanguage; public DefaultActiveRules(Collection<NewActiveRule> newActiveRules) { ImmutableListMultimap.Builder<String, ActiveRule> repoBuilder = ImmutableListMultimap.builder(); ImmutableListMultimap.Builder<String, ActiveRule> langBuilder = ImmutableListMultimap.builder(); for (NewActiveRule newAR : newActiveRules) { DefaultActiveRule ar = new DefaultActiveRule(newAR); - repoBuilder.put(ar.ruleKey().repository(), ar); + String repo = ar.ruleKey().repository(); + repoBuilder.put(repo, ar); if (ar.language() != null) { langBuilder.put(ar.language(), ar); } - if (!activeRulesByRepositoryAndKey.containsKey(ar.ruleKey().repository())) { - activeRulesByRepositoryAndKey.put(ar.ruleKey().repository(), new HashMap<String, ActiveRule>()); - activeRulesByRepositoryAndInternalKey.put(ar.ruleKey().repository(), new HashMap<String, ActiveRule>()); - } - activeRulesByRepositoryAndKey.get(ar.ruleKey().repository()).put(ar.ruleKey().rule(), ar); + + activeRulesByRepositoryAndKey.computeIfAbsent(repo, r -> new HashMap<>()).put(ar.ruleKey().rule(), ar); String internalKey = ar.internalKey(); if (internalKey != null) { - activeRulesByRepositoryAndInternalKey.get(ar.ruleKey().repository()).put(internalKey, ar); + activeRulesByRepositoryAndInternalKey.computeIfAbsent(repo, r -> new HashMap<>()).put(internalKey, ar); } } activeRulesByRepository = repoBuilder.build(); @@ -65,11 +61,8 @@ public class DefaultActiveRules implements ActiveRules { @Override public ActiveRule find(RuleKey ruleKey) { - Map<String, ActiveRule> map = activeRulesByRepositoryAndKey.get(ruleKey.repository()); - if(map != null) { - return map.get(ruleKey.rule()); - } - return null; + return activeRulesByRepositoryAndKey.getOrDefault(ruleKey.repository(), Collections.emptyMap()) + .get(ruleKey.rule()); } @Override diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java index 37795039e83..1b46101fe24 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/rule/internal/DefaultRules.java @@ -25,7 +25,6 @@ import com.google.common.collect.HashBasedTable; import org.sonar.api.batch.rule.Rule; import com.google.common.collect.Table; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.collect.ListMultimap; import org.apache.commons.lang.StringUtils; import org.sonar.api.batch.rule.Rules; import org.sonar.api.rule.RuleKey; @@ -39,9 +38,7 @@ import java.util.List; @Immutable class DefaultRules implements Rules { - - // TODO use disk-backed cache (persistit) instead of full in-memory cache ? - private final ListMultimap<String, Rule> rulesByRepository; + private final ImmutableListMultimap<String, Rule> rulesByRepository; private final ImmutableTable<String, String, List<Rule>> rulesByRepositoryAndInternalKey; DefaultRules(Collection<NewRule> newRules) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java index abdb03c9ecc..88e9c1d6b13 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java @@ -57,7 +57,7 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens { this.inputFile = requireNonNull(inputFile, "file can't be null"); String[] cpdExclusions = config.getStringArray(CoreProperties.CPD_EXCLUSIONS); for (PathPattern cpdExclusion : PathPattern.create(cpdExclusions)) { - if (cpdExclusion.match(inputFile)) { + if (cpdExclusion.match(inputFile.absolutePath(), inputFile.relativePath())) { this.excluded = true; } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java index 4cb9ded0ef5..65752a177c6 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/resources/Project.java @@ -28,6 +28,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.InputModule; +import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.component.Component; import org.sonar.api.scan.filesystem.PathResolver; @@ -39,6 +40,10 @@ import org.sonar.api.scan.filesystem.PathResolver; public class Project extends Resource implements Component { private final ProjectDefinition definition; + public Project(DefaultInputModule module) { + this(module.definition()); + } + public Project(ProjectDefinition definition) { this.definition = definition; this.setKey(definition.getKey()); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/PathResolver.java b/sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/PathResolver.java index fdc1dd19afd..17698835e8e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/PathResolver.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/PathResolver.java @@ -25,6 +25,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.Immutable; + import org.apache.commons.io.FilenameUtils; import org.sonar.api.batch.ScannerSide; import org.sonar.api.utils.PathUtils; @@ -35,6 +37,7 @@ import static java.util.stream.Collectors.joining; * @since 3.5 */ @ScannerSide +@Immutable public class PathResolver { public File relativeFile(File dir, String path) { diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/FilterableIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/FilterableIssue.java index 6928b75e036..76e1a74ec29 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/FilterableIssue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/FilterableIssue.java @@ -22,12 +22,14 @@ package org.sonar.api.scan.issue.filter; import java.util.Date; import javax.annotation.CheckForNull; +import javax.annotation.concurrent.ThreadSafe; import org.sonar.api.rule.RuleKey; /** * @since 5.3 */ +@ThreadSafe public interface FilterableIssue { String componentKey(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java index 230ad24605d..0c2847f7a1e 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilter.java @@ -19,6 +19,9 @@ */ package org.sonar.api.scan.issue.filter; + +import javax.annotation.concurrent.ThreadSafe; + import org.sonar.api.ExtensionPoint; import org.sonar.api.batch.ScannerSide; import org.sonarsource.api.sonarlint.SonarLintSide; @@ -27,6 +30,7 @@ import org.sonarsource.api.sonarlint.SonarLintSide; @SonarLintSide @ExtensionPoint @FunctionalInterface +@ThreadSafe /** * @since 5.3 */ @@ -40,6 +44,9 @@ public interface IssueFilter { * </ul> * The <code>chain</code> parameter allows for fine control of the filtering logic: it is each filter's duty to either pass the issue to the next filter, by calling * the {@link IssueFilterChain#accept} method, or return directly if the issue has to be accepted or not + * + * Implementations should be thread safe. + * * @param issue the issue being filtered * @param chain the rest of the filters * @return <code>true</code> to accept the issue, <code>false</code> to reject it, {@link IssueFilterChain#accept} to let the other filters decide. diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilterChain.java b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilterChain.java index aab918618ce..02b6943b63f 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilterChain.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/scan/issue/filter/IssueFilterChain.java @@ -19,6 +19,8 @@ */ package org.sonar.api.scan.issue.filter; +import javax.annotation.concurrent.ThreadSafe; + /** * A filter chain is an object provided to issues filters for fine control over the filtering logic. Each filter has the choice to: * <ul> @@ -29,6 +31,7 @@ package org.sonar.api.scan.issue.filter; * * @since 5.3 */ +@ThreadSafe public interface IssueFilterChain { /** * Called by a filter to let downstream filters decide the fate of the issue diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java index a1a0f064569..310e788d36d 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/WildcardPattern.java @@ -19,10 +19,13 @@ */ package org.sonar.api.utils; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; + import org.apache.commons.lang.StringUtils; /** @@ -54,12 +57,12 @@ import org.apache.commons.lang.StringUtils; * <a href="https://github.com/JetBrains/intellij-community/blob/idea/107.743/platform/util/src/com/intellij/openapi/util/io/FileUtil.java#L847">FileUtil</a> * from IntelliJ OpenAPI. * - * * @since 1.10 */ +@ThreadSafe public class WildcardPattern { - private static final Map<String, WildcardPattern> CACHE = new HashMap<>(); + private static final Map<String, WildcardPattern> CACHE = Collections.synchronizedMap(new HashMap<>()); private static final String SPECIAL_CHARS = "()[]^$.{}+|"; private Pattern pattern; @@ -196,11 +199,6 @@ public class WildcardPattern { */ public static WildcardPattern create(String pattern, String directorySeparator) { String key = pattern + directorySeparator; - WildcardPattern wildcardPattern = CACHE.get(key); - if (wildcardPattern == null) { - wildcardPattern = new WildcardPattern(pattern, directorySeparator); - CACHE.put(key, wildcardPattern); - } - return wildcardPattern; + return CACHE.computeIfAbsent(key, k -> new WildcardPattern(pattern, directorySeparator)); } } |