]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-926 improve computation of number of lines
authorSimon Brandhof <simon.brandhof@gmail.com>
Mon, 10 Feb 2014 15:00:27 +0000 (16:00 +0100)
committerSimon Brandhof <simon.brandhof@gmail.com>
Mon, 10 Feb 2014 15:23:07 +0000 (16:23 +0100)
24 files changed:
sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileHashDigest.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileHashes.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileIndex.java
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileMetadata.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetection.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactory.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PreviousFileHashLoader.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/RemoteFileHashes.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileHashDigestTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileHashesTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileIndexTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileMetadataTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/PreviousFileHashesLoaderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/RemoteFileHashesTest.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/internal/DefaultInputDir.java
sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/internal/DefaultInputFile.java

index e7ca7567eb9e8e6a0d71bee7556fdddee7e7229d..b7d737f01cb269a76087a3a3268986494fc93138 100644 (file)
@@ -28,12 +28,7 @@ import org.sonar.api.batch.rule.CheckFactory;
 import org.sonar.api.platform.ComponentContainer;
 import org.sonar.api.resources.Project;
 import org.sonar.api.scan.filesystem.FileExclusions;
-import org.sonar.batch.DefaultProjectClasspath;
-import org.sonar.batch.DefaultSensorContext;
-import org.sonar.batch.DefaultTimeMachine;
-import org.sonar.batch.ProjectTree;
-import org.sonar.batch.ResourceFilters;
-import org.sonar.batch.ViolationFilters;
+import org.sonar.batch.*;
 import org.sonar.batch.bootstrap.BatchExtensionDictionnary;
 import org.sonar.batch.bootstrap.ExtensionInstaller;
 import org.sonar.batch.bootstrap.ExtensionMatcher;
@@ -51,17 +46,7 @@ import org.sonar.batch.rule.ModuleQProfiles;
 import org.sonar.batch.rule.ModuleRulesProvider;
 import org.sonar.batch.rule.QProfileSensor;
 import org.sonar.batch.rule.RulesProfileProvider;
-import org.sonar.batch.scan.filesystem.ComponentIndexer;
-import org.sonar.batch.scan.filesystem.DefaultModuleFileSystem;
-import org.sonar.batch.scan.filesystem.DeprecatedFileFilters;
-import org.sonar.batch.scan.filesystem.ExclusionFilters;
-import org.sonar.batch.scan.filesystem.FileHashes;
-import org.sonar.batch.scan.filesystem.FileIndex;
-import org.sonar.batch.scan.filesystem.FileSystemLogger;
-import org.sonar.batch.scan.filesystem.LanguageRecognizer;
-import org.sonar.batch.scan.filesystem.ModuleFileSystemInitializer;
-import org.sonar.batch.scan.filesystem.ProjectFileSystemAdapter;
-import org.sonar.batch.scan.filesystem.RemoteFileHashes;
+import org.sonar.batch.scan.filesystem.*;
 import org.sonar.batch.scan.language.DefaultModuleLanguages;
 import org.sonar.batch.scan.report.ComponentSelectorFactory;
 import org.sonar.batch.scan.report.JsonReport;
@@ -107,12 +92,13 @@ public class ModuleScanContainer extends ComponentContainer {
       FileExclusions.class,
       ExclusionFilters.class,
       DeprecatedFileFilters.class,
-      FileHashes.class,
-      RemoteFileHashes.class,
+      InputFileBuilderFactory.class,
+      StatusDetectionFactory.class,
+      LanguageDetectionFactory.class,
+      PreviousFileHashLoader.class,
       FileIndex.class,
       ComponentIndexer.class,
       DefaultModuleLanguages.class,
-      LanguageRecognizer.class,
       FileSystemLogger.class,
       DefaultProjectClasspath.class,
       DefaultModuleFileSystem.class,
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileHashDigest.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileHashDigest.java
deleted file mode 100644 (file)
index e7f5cb3..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import org.apache.commons.codec.binary.Hex;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.apache.commons.io.IOUtils;
-
-import java.io.*;
-import java.nio.charset.Charset;
-import java.security.MessageDigest;
-
-/**
- * Computes hash of files. Ends of Lines are ignored, so files with
- * same content but different EOL encoding have the same hash.
- */
-class FileHashDigest {
-
-  // This singleton aims only to increase the coverage by allowing
-  // to test the private method !
-  static final FileHashDigest INSTANCE = new FileHashDigest();
-
-  private FileHashDigest() {
-  }
-
-  /**
-   * Compute hash of a file ignoring line ends differences.
-   * Maximum performance is needed.
-   */
-  String hash(File file, Charset charset) {
-    Reader reader = null;
-    try {
-      MessageDigest md5Digest = DigestUtils.getMd5Digest();
-      md5Digest.reset();
-      reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
-      int i = reader.read();
-      boolean afterCR = true;
-      while (i != -1) {
-        char c = (char) i;
-        if (afterCR) {
-          afterCR = false;
-          if (c == '\n') {
-            // Ignore
-            i = reader.read();
-            continue;
-          }
-        }
-        if (c == '\r') {
-          afterCR = true;
-          c = '\n';
-        }
-        md5Digest.update(charToBytesUTF(c));
-        i = reader.read();
-      }
-      return Hex.encodeHexString(md5Digest.digest());
-    } catch (IOException e) {
-      throw new IllegalStateException(String.format("Fail to compute hash of file %s with charset %s", file.getAbsolutePath(), charset), e);
-    } finally {
-      IOUtils.closeQuietly(reader);
-    }
-  }
-
-  private byte[] charToBytesUTF(char c) {
-    char[] buffer = new char[]{c};
-    byte[] b = new byte[buffer.length << 1];
-    for (int i = 0; i < buffer.length; i++) {
-      int bpos = i << 1;
-      b[bpos] = (byte) ((buffer[i] & 0xFF00) >> 8);
-      b[bpos + 1] = (byte) (buffer[i] & 0x00FF);
-    }
-    return b;
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileHashes.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileHashes.java
deleted file mode 100644 (file)
index 5ffcc11..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import org.sonar.api.BatchComponent;
-
-import javax.annotation.CheckForNull;
-import java.io.File;
-import java.nio.charset.Charset;
-
-/**
- * Facade for local and remote file hashes
- */
-public class FileHashes implements BatchComponent {
-
-  private final RemoteFileHashes remoteFileHashes;
-
-  public FileHashes(RemoteFileHashes remoteFileHashes) {
-    this.remoteFileHashes = remoteFileHashes;
-  }
-
-  @CheckForNull
-  public String hash(File file, Charset charset) {
-    return FileHashDigest.INSTANCE.hash(file, charset);
-  }
-
-  @CheckForNull
-  public String remoteHash(String baseRelativePath) {
-    return remoteFileHashes.remoteHash(baseRelativePath);
-  }
-}
index a6af93b12470dabaf60088248f30140e0c7d297e..4c037133b8fdd9c2fa5fc1f8929a2f0d9ba96436 100644 (file)
  */
 package org.sonar.batch.scan.filesystem;
 
-import com.google.common.collect.Maps;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.filefilter.FileFilterUtils;
 import org.apache.commons.io.filefilter.HiddenFileFilter;
 import org.apache.commons.io.filefilter.IOFileFilter;
-import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.BatchComponent;
-import org.sonar.api.resources.Java;
-import org.sonar.api.resources.JavaFile;
 import org.sonar.api.resources.Project;
 import org.sonar.api.scan.filesystem.InputDir;
 import org.sonar.api.scan.filesystem.InputFile;
-import org.sonar.api.scan.filesystem.InputFileFilter;
-import org.sonar.api.scan.filesystem.ModuleFileSystem;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.api.scan.filesystem.internal.DefaultInputDir;
-import org.sonar.api.scan.filesystem.internal.DefaultInputFile;
+import org.sonar.api.scan.filesystem.InputFileFilter;
 import org.sonar.api.utils.PathUtils;
 import org.sonar.api.utils.SonarException;
 
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
 import java.io.File;
-import java.nio.charset.Charset;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -85,19 +75,17 @@ public class FileIndex implements BatchComponent {
 
   private final PathResolver pathResolver;
   private final List<InputFileFilter> filters;
-  private final LanguageRecognizer languageRecognizer;
   private final InputFileCache fileCache;
-  private final FileHashes fileHashes;
   private final Project module;
   private final ExclusionFilters exclusionFilters;
+  private final InputFileBuilderFactory inputFileBuilderFactory;
 
-  public FileIndex(List<InputFileFilter> filters, ExclusionFilters exclusionFilters, LanguageRecognizer languageRecognizer,
-    InputFileCache cache, FileHashes fileHashes, PathResolver pathResolver, Project project) {
+  public FileIndex(List<InputFileFilter> filters, ExclusionFilters exclusionFilters, InputFileBuilderFactory inputFileBuilderFactory,
+                   InputFileCache cache, PathResolver pathResolver, Project project) {
     this.filters = filters;
     this.exclusionFilters = exclusionFilters;
-    this.languageRecognizer = languageRecognizer;
+    this.inputFileBuilderFactory = inputFileBuilderFactory;
     this.fileCache = cache;
-    this.fileHashes = fileHashes;
     this.pathResolver = pathResolver;
     this.module = project;
   }
@@ -114,13 +102,14 @@ public class FileIndex implements BatchComponent {
 
     Progress progress = new Progress(fileCache.fileRelativePaths(fileSystem.moduleKey()));
 
+    InputFileBuilder inputFileBuilder = inputFileBuilderFactory.create(fileSystem);
     if (!fileSystem.sourceFiles().isEmpty() || !fileSystem.testFiles().isEmpty()) {
       // Index only provided files
-      indexFiles(fileSystem, progress, fileSystem.sourceFiles(), InputFile.TYPE_MAIN);
-      indexFiles(fileSystem, progress, fileSystem.testFiles(), InputFile.TYPE_TEST);
+      indexFiles(inputFileBuilder, fileSystem, progress, fileSystem.sourceFiles(), InputFile.TYPE_MAIN);
+      indexFiles(inputFileBuilder, fileSystem, progress, fileSystem.testFiles(), InputFile.TYPE_TEST);
     } else if (fileSystem.baseDir() != null) {
       // index from basedir
-      indexDirectory(fileSystem, progress, fileSystem.baseDir());
+      indexDirectory(inputFileBuilder, fileSystem, progress, fileSystem.baseDir());
     }
 
     // Remove files that have been removed since previous indexation
@@ -132,16 +121,16 @@ public class FileIndex implements BatchComponent {
 
   }
 
-  private void indexFiles(DefaultModuleFileSystem fileSystem, Progress progress, List<File> sourceFiles, String type) {
+  private void indexFiles(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fileSystem, Progress progress, List<File> sourceFiles, String type) {
     for (File sourceFile : sourceFiles) {
       String path = pathResolver.relativePath(fileSystem.baseDir(), sourceFile);
       if (path == null) {
         LoggerFactory.getLogger(getClass()).warn(String.format(
           "File '%s' is not declared in module basedir %s", sourceFile.getAbsoluteFile(), fileSystem.baseDir()
-          ));
+        ));
       } else {
         if (exclusionFilters.accept(sourceFile, path, type)) {
-          indexFile(fileSystem, progress, sourceFile, path, type);
+          indexFile(inputFileBuilder, fileSystem, progress, sourceFile, path, type);
         }
       }
     }
@@ -158,37 +147,35 @@ public class FileIndex implements BatchComponent {
 
   InputDir inputDir(DefaultModuleFileSystem fileSystem, File ioFile) {
     String path = computeFilePath(fileSystem, ioFile);
-    // TODO no cache for InputDir
-    Map<String, String> attributes = Maps.newHashMap();
-    // paths
+    DefaultInputDir inputDir = new DefaultInputDir(FilenameUtils.normalize(ioFile.getAbsolutePath(), true), path);
     String resourceKey = PathUtils.sanitize(path);
-    set(attributes, DefaultInputFile.ATTRIBUTE_COMPONENT_KEY, module.getEffectiveKey() + ":" + resourceKey);
-    return DefaultInputDir.create(ioFile, path, attributes);
+    inputDir.setKey(module.getEffectiveKey() + ":" + resourceKey);
+    return inputDir;
   }
 
-  private void indexDirectory(DefaultModuleFileSystem fileSystem, Progress status, File dirToIndex) {
+  private void indexDirectory(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fileSystem, Progress status, File dirToIndex) {
     Collection<File> files = FileUtils.listFiles(dirToIndex, FILE_FILTER, DIR_FILTER);
     for (File sourceFile : files) {
       String path = pathResolver.relativePath(fileSystem.baseDir(), sourceFile);
       if (path == null) {
         LoggerFactory.getLogger(getClass()).warn(String.format(
           "File '%s' is not declared in module basedir %s", sourceFile.getAbsoluteFile(), fileSystem.baseDir()
-          ));
+        ));
       } else {
         if (exclusionFilters.accept(sourceFile, path, InputFile.TYPE_MAIN)) {
-          indexFile(fileSystem, status, sourceFile, path, InputFile.TYPE_MAIN);
+          indexFile(inputFileBuilder, fileSystem, status, sourceFile, path, InputFile.TYPE_MAIN);
         }
         if (exclusionFilters.accept(sourceFile, path, InputFile.TYPE_TEST)) {
-          indexFile(fileSystem, status, sourceFile, path, InputFile.TYPE_TEST);
+          indexFile(inputFileBuilder, fileSystem, status, sourceFile, path, InputFile.TYPE_TEST);
         }
       }
     }
   }
 
-  private void indexFile(DefaultModuleFileSystem fileSystem, Progress status, File file, String path, String type) {
-    InputFile input = newInputFile(fileSystem, type, file, path);
-    if (input != null && accept(input)) {
-      fileCache.put(fileSystem.moduleKey(), input);
+  private void indexFile(InputFileBuilder inputFileBuilder, DefaultModuleFileSystem fs, Progress status, File file, String path, String type) {
+    InputFile inputFile = inputFileBuilder.create(file, type);
+    if (inputFile != null && accept(inputFile)) {
+      fileCache.put(fs.moduleKey(), inputFile);
       status.markAsIndexed(path);
     }
   }
@@ -197,67 +184,6 @@ public class FileIndex implements BatchComponent {
     return pathResolver.relativePath(fileSystem.baseDir(), file);
   }
 
-  @CheckForNull
-  private InputFile newInputFile(ModuleFileSystem fileSystem, String type, File file, String path) {
-
-    Map<String, String> attributes = Maps.newHashMap();
-    set(attributes, InputFile.ATTRIBUTE_TYPE, type);
-
-    String resourceKey = PathUtils.sanitize(path);
-    set(attributes, DefaultInputFile.ATTRIBUTE_COMPONENT_KEY, module.getEffectiveKey() + ":" + resourceKey);
-    // hash + status
-    initStatus(file, fileSystem.sourceCharset(), path, attributes);
-
-    DefaultInputFile inputFile = DefaultInputFile.create(file, fileSystem.sourceCharset(), path, attributes);
-    String lang = languageRecognizer.of(inputFile);
-    if (lang == null) {
-      return null;
-    }
-    set(inputFile.attributes(), InputFile.ATTRIBUTE_LANGUAGE, lang);
-
-    setDeprecatedAttributes(fileSystem, type, file, attributes, inputFile, lang);
-
-    return inputFile;
-  }
-
-  private void setDeprecatedAttributes(ModuleFileSystem fileSystem, String type, File file, Map<String, String> attributes, DefaultInputFile inputFile, String lang) {
-    List<File> sourceDirs = InputFile.TYPE_MAIN.equals(type) ? fileSystem.sourceDirs() : fileSystem.testDirs();
-    for (File src : sourceDirs) {
-      String sourceRelativePath = pathResolver.relativePath(src, file);
-      if (sourceRelativePath != null) {
-        set(attributes, DefaultInputFile.ATTRIBUTE_SOURCEDIR_PATH, PathUtils.canonicalPath(src));
-        set(attributes, DefaultInputFile.ATTRIBUTE_SOURCE_RELATIVE_PATH, sourceRelativePath);
-        if (Java.KEY.equals(lang)) {
-          set(inputFile.attributes(), DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, module.getEffectiveKey() + ":"
-            + JavaFile.fromRelativePath(sourceRelativePath, false).getDeprecatedKey());
-        } else {
-          set(inputFile.attributes(), DefaultInputFile.ATTRIBUTE_COMPONENT_DEPRECATED_KEY, module.getEffectiveKey() + ":" + sourceRelativePath);
-        }
-        return;
-      }
-    }
-  }
-
-  private void initStatus(File file, Charset charset, String baseRelativePath, Map<String, String> attributes) {
-    String hash = fileHashes.hash(file, charset);
-    set(attributes, DefaultInputFile.ATTRIBUTE_HASH, hash);
-
-    String remoteHash = fileHashes.remoteHash(baseRelativePath);
-    // currently no need to store this remote hash in attributes
-    if (StringUtils.equals(hash, remoteHash)) {
-      set(attributes, InputFile.ATTRIBUTE_STATUS, InputFile.STATUS_SAME);
-    } else if (StringUtils.isEmpty(remoteHash)) {
-      set(attributes, InputFile.ATTRIBUTE_STATUS, InputFile.STATUS_ADDED);
-    } else {
-      set(attributes, InputFile.ATTRIBUTE_STATUS, InputFile.STATUS_CHANGED);
-    }
-  }
-
-  private void set(Map<String, String> attributes, String key, @Nullable String value) {
-    if (value != null) {
-      attributes.put(key, value);
-    }
-  }
 
   private boolean accept(InputFile inputFile) {
     // InputFileFilter extensions
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileMetadata.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/FileMetadata.java
new file mode 100644 (file)
index 0000000..298eeef
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+
+/**
+ * Computes hash of files. Ends of Lines are ignored, so files with
+ * same content but different EOL encoding have the same hash.
+ */
+class FileMetadata {
+
+  private static final char LINE_FEED = '\n';
+  private static final char CARRIAGE_RETURN = '\r';
+
+  // This singleton aims only to increase the coverage by allowing
+  // to test the private method !
+  static final FileMetadata INSTANCE = new FileMetadata();
+
+  private FileMetadata() {
+  }
+
+  /**
+   * Compute hash of a file ignoring line ends differences.
+   * Maximum performance is needed.
+   */
+  Metadata read(File file, Charset encoding) {
+    Reader reader = null;
+    long lines = 0L;
+    char c = (char)-1;
+    try {
+      MessageDigest md5Digest = DigestUtils.getMd5Digest();
+      md5Digest.reset();
+      reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
+      int i = reader.read();
+      boolean afterCR = true;
+      while (i != -1) {
+        c = (char) i;
+        if (afterCR) {
+          afterCR = false;
+          if (c == LINE_FEED) {
+            // Ignore
+            i = reader.read();
+            lines++;
+            continue;
+          }
+        }
+        if (c == CARRIAGE_RETURN) {
+          afterCR = true;
+          c = LINE_FEED;
+        } else if (c == LINE_FEED) {
+          lines++;
+        }
+        md5Digest.update(charToBytesUTF(c));
+        i = reader.read();
+      }
+      if (c != LINE_FEED) {
+        lines++;
+      }
+      String hash = Hex.encodeHexString(md5Digest.digest());
+      return new Metadata(lines, hash);
+
+    } catch (IOException e) {
+      throw new IllegalStateException(String.format("Fail to read file '%s' with encoding '%s'", file.getAbsolutePath(), encoding), e);
+    } finally {
+      IOUtils.closeQuietly(reader);
+    }
+  }
+
+  private byte[] charToBytesUTF(char c) {
+    char[] buffer = new char[]{c};
+    byte[] b = new byte[buffer.length << 1];
+    for (int i = 0; i < buffer.length; i++) {
+      int bpos = i << 1;
+      b[bpos] = (byte) ((buffer[i] & 0xFF00) >> 8);
+      b[bpos + 1] = (byte) (buffer[i] & 0x00FF);
+    }
+    return b;
+  }
+
+  static class Metadata {
+    long lines;
+    String hash;
+
+    private Metadata(long lines, String hash) {
+      this.lines = lines;
+      this.hash = hash;
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilder.java
new file mode 100644 (file)
index 0000000..44409fb
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import com.google.common.collect.Maps;
+import org.apache.commons.io.FilenameUtils;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.scan.filesystem.InputFile;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.api.scan.filesystem.internal.DefaultInputFile;
+import org.sonar.api.utils.MessageException;
+
+import javax.annotation.CheckForNull;
+import java.io.File;
+import java.util.List;
+
+class InputFileBuilder {
+
+  private final String moduleKey;
+  private final PathResolver pathResolver;
+  private final LanguageDetection langDetection;
+  private final StatusDetection statusDetection;
+  private final DefaultModuleFileSystem fs;
+
+  InputFileBuilder(String moduleKey, PathResolver pathResolver, LanguageDetection langDetection,
+                   StatusDetection statusDetection, DefaultModuleFileSystem fs) {
+    this.moduleKey = moduleKey;
+    this.pathResolver = pathResolver;
+    this.langDetection = langDetection;
+    this.statusDetection = statusDetection;
+    this.fs = fs;
+  }
+
+  @CheckForNull
+  DefaultInputFile create(File file, String type) {
+    String relativePath = pathResolver.relativePath(fs.baseDir(), file);
+    if (relativePath == null) {
+      throw MessageException.of(String.format("File '%s' is ignored. It is not in module basedir '%s'.", file.getAbsolutePath(), fs.baseDir()));
+    }
+    DefaultInputFile inputFile = DefaultInputFile.create(file, fs.sourceCharset(), relativePath, Maps.<String, String>newHashMap());
+    inputFile.setType(type);
+    inputFile.setKey(moduleKey + ":" + inputFile.path());
+    String lang = langDetection.language(inputFile);
+    if (lang == null) {
+      // TODO use a default plain-text language ?
+      return null;
+    }
+    inputFile.setLanguage(lang);
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(inputFile.file(), fs.sourceCharset());
+    inputFile.setLines(metadata.lines);
+    inputFile.setHash(metadata.hash);
+    inputFile.setStatus(statusDetection.status(inputFile.path(), metadata.hash));
+    fillDeprecatedData(inputFile);
+    return inputFile;
+  }
+
+  private void fillDeprecatedData(DefaultInputFile inputFile) {
+    List<File> sourceDirs = InputFile.TYPE_MAIN.equals(inputFile.type()) ? fs.sourceDirs() : fs.testDirs();
+    for (File sourceDir : sourceDirs) {
+      String sourceRelativePath = pathResolver.relativePath(sourceDir, inputFile.file());
+      if (sourceRelativePath != null) {
+        inputFile.setPathRelativeToSourceDir(sourceRelativePath);
+        inputFile.setSourceDirAbsolutePath(FilenameUtils.normalize(sourceDir.getAbsolutePath(), true));
+
+        if (Java.KEY.equals(inputFile.language())) {
+          inputFile.setDeprecatedKey(moduleKey + ":" + JavaFile.fromRelativePath(sourceRelativePath, false).getDeprecatedKey());
+        } else {
+          inputFile.setDeprecatedKey(moduleKey + ":" + sourceRelativePath);
+        }
+      }
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputFileBuilderFactory.java
new file mode 100644 (file)
index 0000000..ce24330
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.resources.Project;
+import org.sonar.api.scan.filesystem.PathResolver;
+
+public class InputFileBuilderFactory implements BatchComponent {
+
+  private final String moduleKey;
+  private final PathResolver pathResolver;
+  private final LanguageDetectionFactory langDetectionFactory;
+  private final StatusDetectionFactory statusDetectionFactory;
+
+  public InputFileBuilderFactory(Project moduleDef, PathResolver pathResolver, LanguageDetectionFactory langDetectionFactory,
+                                 StatusDetectionFactory statusDetectionFactory) {
+    this.moduleKey = moduleDef.getEffectiveKey();
+    this.pathResolver = pathResolver;
+    this.langDetectionFactory = langDetectionFactory;
+    this.statusDetectionFactory = statusDetectionFactory;
+  }
+
+  InputFileBuilder create(DefaultModuleFileSystem fs) {
+    return new InputFileBuilder(moduleKey, pathResolver, langDetectionFactory.create(), statusDetectionFactory.create(), fs);
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetection.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetection.java
new file mode 100644 (file)
index 0000000..a8a7019
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.scan.filesystem.InputFile;
+import org.sonar.api.utils.MessageException;
+
+import javax.annotation.CheckForNull;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Detect language of a source file based on its suffix and configured patterns.
+ */
+class LanguageDetection {
+
+  private static final Logger LOG = LoggerFactory.getLogger(LanguageDetection.class);
+
+  /**
+   * Lower-case extension -> languages
+   */
+  private final Map<String, PathPattern[]> patternByLanguage = Maps.newLinkedHashMap();
+  private final List<String> languagesToConsider = Lists.newArrayList();
+  private final String forcedLanguage;
+
+  LanguageDetection(Settings settings, Languages languages) {
+    for (Language language : languages.all()) {
+      String[] filePatterns = settings.getStringArray(getFileLangPatternPropKey(language.getKey()));
+      PathPattern[] pathPatterns = PathPattern.create(filePatterns);
+      if (pathPatterns.length > 0) {
+        patternByLanguage.put(language.getKey(), pathPatterns);
+      } else {
+        // If no custom language pattern is defined then fallback to suffixes declared by language
+        String[] patterns = Arrays.copyOf(language.getFileSuffixes(), language.getFileSuffixes().length);
+        for (int i = 0; i < patterns.length; i++) {
+          String suffix = patterns[i];
+          String extension = sanitizeExtension(suffix);
+          patterns[i] = "**/*." + extension;
+        }
+        PathPattern[] defaultLanguagePatterns = PathPattern.create(patterns);
+        patternByLanguage.put(language.getKey(), defaultLanguagePatterns);
+        LOG.debug("Declared extensions of language " + language + " were converted to " + getDetails(language.getKey()));
+      }
+    }
+
+    forcedLanguage = StringUtils.defaultIfBlank(settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY), null);
+    // First try with lang patterns
+    if (forcedLanguage != null) {
+      if (!patternByLanguage.containsKey(forcedLanguage)) {
+        throw MessageException.of("No language is installed with key '" + forcedLanguage + "'. Please update property '" + CoreProperties.PROJECT_LANGUAGE_PROPERTY + "'");
+      }
+      languagesToConsider.add(forcedLanguage);
+    } else {
+      languagesToConsider.addAll(patternByLanguage.keySet());
+    }
+  }
+
+  @CheckForNull
+  String language(InputFile inputFile) {
+    String detectedLanguage = null;
+    for (String languageKey : languagesToConsider) {
+      PathPattern[] patterns = patternByLanguage.get(languageKey);
+      if (patterns != null) {
+        for (PathPattern pathPattern : patterns) {
+          if (pathPattern.match(inputFile, false)) {
+            if (detectedLanguage == null) {
+              detectedLanguage = languageKey;
+              break;
+            } else {
+              // Language was already forced by another pattern
+              throw MessageException.of("Language of file '" + inputFile.path() + "' can not be decided as the file matches patterns of both " + getDetails(detectedLanguage)
+                + " and " + getDetails(languageKey));
+            }
+          }
+        }
+      }
+    }
+    if (detectedLanguage != null) {
+      LOG.debug("Language of file '" + inputFile.path() + "' was detected to be '" + detectedLanguage + "'");
+      return detectedLanguage;
+    }
+
+    // Check if deprecated sonar.language is used and we are on a language without declared extensions
+    if (forcedLanguage != null) {
+      // Languages without declared suffixes match everything
+      if (patternByLanguage.get(forcedLanguage).length == 0) {
+        return forcedLanguage;
+      }
+    }
+    return null;
+  }
+
+  private String getFileLangPatternPropKey(String languageKey) {
+    return "sonar.lang.patterns." + languageKey;
+  }
+
+  private String getDetails(String detectedLanguage) {
+    return getFileLangPatternPropKey(detectedLanguage) + " : " + Joiner.on(",").join(patternByLanguage.get(detectedLanguage));
+  }
+
+  static String sanitizeExtension(String suffix) {
+    return StringUtils.lowerCase(StringUtils.removeStart(suffix, "."));
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactory.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageDetectionFactory.java
new file mode 100644 (file)
index 0000000..eb13b62
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Languages;
+
+public class LanguageDetectionFactory implements BatchComponent {
+  private final Settings settings;
+  private final Languages languages;
+
+  public LanguageDetectionFactory(Settings settings, Languages languages) {
+    this.settings = settings;
+    this.languages = languages;
+  }
+
+  public LanguageDetection create() {
+    return new LanguageDetection(settings, languages);
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/LanguageRecognizer.java
deleted file mode 100644 (file)
index 6e2789b..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import com.google.common.base.Joiner;
-import com.google.common.collect.Maps;
-import org.apache.commons.lang.StringUtils;
-import org.picocontainer.Startable;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.scan.filesystem.InputFile;
-import org.sonar.api.utils.SonarException;
-
-import javax.annotation.CheckForNull;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Detect language of source files.
- */
-public class LanguageRecognizer implements BatchComponent, Startable {
-
-  private static final Logger LOG = LoggerFactory.getLogger(LanguageRecognizer.class);
-
-  private final Languages languages;
-
-  /**
-   * Lower-case extension -> languages
-   */
-  private Map<String, PathPattern[]> patternByLanguage = Maps.newLinkedHashMap();
-
-  private Settings settings;
-
-  public LanguageRecognizer(Settings settings, Languages languages) {
-    this.settings = settings;
-    this.languages = languages;
-  }
-
-  @Override
-  public void start() {
-    for (Language language : languages.all()) {
-      String[] filePatterns = settings.getStringArray(getFileLangPatternPropKey(language.getKey()));
-      PathPattern[] pathPatterns = PathPattern.create(filePatterns);
-      if (pathPatterns.length > 0) {
-        patternByLanguage.put(language.getKey(), pathPatterns);
-      } else if (language.getFileSuffixes().length > 0) {
-        // If no custom language pattern is defined then fallback to suffixes declared by language
-        String[] patterns = Arrays.copyOf(language.getFileSuffixes(), language.getFileSuffixes().length);
-        for (int i = 0; i < patterns.length; i++) {
-          String suffix = patterns[i];
-          String extension = sanitizeExtension(suffix);
-          patterns[i] = "**/*." + extension;
-        }
-        PathPattern[] defaultLanguagePatterns = PathPattern.create(patterns);
-        patternByLanguage.put(language.getKey(), defaultLanguagePatterns);
-        LOG.debug("Declared extensions of language " + language + " were converted to " + getDetails(language.getKey()));
-      }
-    }
-  }
-
-  private String getFileLangPatternPropKey(String languageKey) {
-    return "sonar.lang.patterns." + languageKey;
-  }
-
-  @Override
-  public void stop() {
-    // do nothing
-  }
-
-  @CheckForNull
-  String of(InputFile inputFile) {
-    String deprecatedLanguageParam = settings.getString(CoreProperties.PROJECT_LANGUAGE_PROPERTY);
-
-    // First try with lang patterns
-    List<String> languagesToConsider = new ArrayList<String>();
-    if (!StringUtils.isBlank(deprecatedLanguageParam)) {
-      languagesToConsider.add(deprecatedLanguageParam);
-    } else {
-      languagesToConsider.addAll(patternByLanguage.keySet());
-    }
-    String detectedLanguage = null;
-    for (String languageKey : languagesToConsider) {
-      PathPattern[] patterns = patternByLanguage.get(languageKey);
-      if (patterns != null) {
-        for (PathPattern pathPattern : patterns) {
-          if (pathPattern.match(inputFile, false)) {
-            if (detectedLanguage == null) {
-              detectedLanguage = languageKey;
-              break;
-            } else {
-              // Language was already forced by another pattern
-              throw new SonarException("Language of file '" + inputFile.path() + "' can not be decided as the file matches patterns of both " + getDetails(detectedLanguage)
-                + " and " + getDetails(languageKey));
-            }
-          }
-        }
-      }
-    }
-    if (detectedLanguage != null) {
-      LOG.debug("Language of file '" + inputFile.path() + "' was detected to be '" + detectedLanguage + "'");
-      return detectedLanguage;
-    }
-
-    // Check if deprecated sonar.language is used and we are on a language without declared extensions
-    if (StringUtils.isNotBlank(deprecatedLanguageParam)) {
-      Language language = languages.get(deprecatedLanguageParam);
-      if (language == null) {
-        throw new SonarException("No language is installed with key '" + deprecatedLanguageParam + "'. Please update property '" + CoreProperties.PROJECT_LANGUAGE_PROPERTY + "'");
-      }
-      // Languages without declared suffixes match everything
-      String[] fileSuffixes = language.getFileSuffixes();
-      if (fileSuffixes.length == 0) {
-        return deprecatedLanguageParam;
-      }
-      return null;
-    }
-
-    return null;
-  }
-
-  private String getDetails(String detectedLanguage) {
-    return getFileLangPatternPropKey(detectedLanguage) + " : " + Joiner.on(",").join(patternByLanguage.get(detectedLanguage));
-  }
-
-  static String sanitizeExtension(String suffix) {
-    return StringUtils.lowerCase(StringUtils.removeStart(suffix, "."));
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PreviousFileHashLoader.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/PreviousFileHashLoader.java
new file mode 100644 (file)
index 0000000..9b6e405
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import com.google.common.collect.Maps;
+import org.picocontainer.Startable;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.utils.KeyValueFormat;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.core.source.SnapshotDataTypes;
+import org.sonar.core.source.db.SnapshotDataDao;
+import org.sonar.core.source.db.SnapshotDataDto;
+
+import javax.annotation.CheckForNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+
+public class PreviousFileHashLoader implements BatchComponent {
+
+  private final SnapshotDataDao dao;
+  private final PastSnapshotFinder pastSnapshotFinder;
+  private final Snapshot snapshot;
+
+  public PreviousFileHashLoader(Snapshot snapshot, SnapshotDataDao dao, PastSnapshotFinder pastSnapshotFinder) {
+    this.snapshot = snapshot;
+    this.dao = dao;
+    this.pastSnapshotFinder = pastSnapshotFinder;
+  }
+
+  /**
+   * Extract hash of the files parsed during the previous analysis
+   */
+  Map<String, String> hashByRelativePath() {
+    Map<String, String> map = Maps.newHashMap();
+    PastSnapshot pastSnapshot = pastSnapshotFinder.findPreviousAnalysis(snapshot);
+    if (pastSnapshot.isRelatedToSnapshot()) {
+      Collection<SnapshotDataDto> selectSnapshotData = dao.selectSnapshotData(
+        pastSnapshot.getProjectSnapshot().getId().longValue(),
+        Arrays.asList(SnapshotDataTypes.FILE_HASHES)
+      );
+      if (!selectSnapshotData.isEmpty()) {
+        SnapshotDataDto snapshotDataDto = selectSnapshotData.iterator().next();
+        String data = snapshotDataDto.getData();
+        map = KeyValueFormat.parse(data);
+      }
+    }
+    return map;
+  }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/RemoteFileHashes.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/RemoteFileHashes.java
deleted file mode 100644 (file)
index ade06ef..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import com.google.common.collect.Maps;
-import org.picocontainer.Startable;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.api.utils.KeyValueFormat;
-import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.PastSnapshotFinder;
-import org.sonar.core.source.SnapshotDataTypes;
-import org.sonar.core.source.db.SnapshotDataDao;
-import org.sonar.core.source.db.SnapshotDataDto;
-
-import javax.annotation.CheckForNull;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Map;
-
-public class RemoteFileHashes implements BatchComponent, Startable {
-
-  private final SnapshotDataDao dao;
-  private final PastSnapshotFinder pastSnapshotFinder;
-  private final Snapshot snapshot;
-
-  private Map<String, String> pathToHash = Maps.newHashMap();
-
-  public RemoteFileHashes(Snapshot snapshot, SnapshotDataDao dao, PastSnapshotFinder pastSnapshotFinder) {
-    this.snapshot = snapshot;
-    this.dao = dao;
-    this.pastSnapshotFinder = pastSnapshotFinder;
-  }
-
-  @Override
-  public void start() {
-    // Extract previous checksum of all files of this module and store them in a map
-    PastSnapshot pastSnapshot = pastSnapshotFinder.findPreviousAnalysis(snapshot);
-    if (pastSnapshot.isRelatedToSnapshot()) {
-      Collection<SnapshotDataDto> selectSnapshotData = dao.selectSnapshotData(
-        pastSnapshot.getProjectSnapshot().getId().longValue(),
-        Arrays.asList(SnapshotDataTypes.FILE_HASHES)
-      );
-      if (!selectSnapshotData.isEmpty()) {
-        SnapshotDataDto snapshotDataDto = selectSnapshotData.iterator().next();
-        String data = snapshotDataDto.getData();
-        pathToHash = KeyValueFormat.parse(data);
-      }
-    }
-  }
-
-  @CheckForNull
-  public String remoteHash(String baseRelativePath) {
-    return pathToHash.get(baseRelativePath);
-  }
-
-  @Override
-  public void stop() {
-    // nothing to do
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetection.java
new file mode 100644 (file)
index 0000000..adadcdf
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.scan.filesystem.InputFile;
+
+import java.util.Map;
+
+class StatusDetection {
+
+  private final Map<String, String> previousHashByRelativePath;
+
+  StatusDetection(Map<String, String> previousHashByRelativePath) {
+    this.previousHashByRelativePath = previousHashByRelativePath;
+  }
+
+  String status(String relativePath, String hash) {
+    String previousHash = previousHashByRelativePath.get(relativePath);
+
+    if (StringUtils.equals(hash, previousHash)) {
+      return InputFile.STATUS_SAME;
+    }
+    if (StringUtils.isEmpty(previousHash)) {
+      return InputFile.STATUS_ADDED;
+    }
+    return InputFile.STATUS_CHANGED;
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/StatusDetectionFactory.java
new file mode 100644 (file)
index 0000000..32bf4dc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import org.sonar.api.BatchComponent;
+
+public class StatusDetectionFactory implements BatchComponent {
+
+  private final PreviousFileHashLoader previousFileHashLoader;
+
+  public StatusDetectionFactory(PreviousFileHashLoader l) {
+    this.previousFileHashLoader = l;
+  }
+
+  StatusDetection create() {
+    return new StatusDetection(previousFileHashLoader.hashByRelativePath());
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileHashDigestTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileHashDigestTest.java
deleted file mode 100644 (file)
index c3345c2..0000000
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import com.google.common.base.Charsets;
-import org.apache.commons.io.FileUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class FileHashDigestTest {
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Test
-  public void should_compute_hash() throws Exception {
-    File tempFile = temp.newFile();
-    FileUtils.write(tempFile, "foo\r\nbar", Charsets.UTF_8, true);
-
-    assertThat(FileHashDigest.INSTANCE.hash(tempFile, Charsets.UTF_8)).isEqualTo("daef8a22a3f12580beadf086a9e11519");
-  }
-
-  @Test
-  public void should_normalize_line_ends() throws Exception {
-    File file1 = temp.newFile();
-    FileUtils.write(file1, "foobar\nfofo", Charsets.UTF_8);
-    String hash1 = FileHashDigest.INSTANCE.hash(file1, Charsets.UTF_8);
-
-    File file2 = temp.newFile();
-    FileUtils.write(file2, "foobar\r\nfofo", Charsets.UTF_8);
-    String hash2 = FileHashDigest.INSTANCE.hash(file2, Charsets.UTF_8);
-
-    File file3 = temp.newFile();
-    FileUtils.write(file3, "foobar\rfofo", Charsets.UTF_8);
-    String hash3 = FileHashDigest.INSTANCE.hash(file3, Charsets.UTF_8);
-
-    File file4 = temp.newFile();
-    FileUtils.write(file4, "foobar\nfofo\n", Charsets.UTF_8);
-    String hash4 = FileHashDigest.INSTANCE.hash(file4, Charsets.UTF_8);
-
-    assertThat(hash1).isEqualTo(hash2);
-    assertThat(hash1).isEqualTo(hash3);
-    assertThat(hash1).isNotEqualTo(hash4);
-  }
-
-  @Test
-  public void should_throw_if_file_does_not_exist() throws Exception {
-    File tempFolder = temp.newFolder();
-    File file = new File(tempFolder, "doesNotExist.txt");
-
-    thrown.expect(IllegalStateException.class);
-    thrown.expectMessage("Fail to compute hash of file " + file.getAbsolutePath() + " with charset UTF-8");
-
-    FileHashDigest.INSTANCE.hash(file, Charsets.UTF_8);
-  }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileHashesTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileHashesTest.java
deleted file mode 100644 (file)
index 98ae99d..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import org.apache.commons.io.FileUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import java.io.File;
-import java.nio.charset.Charset;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class FileHashesTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  RemoteFileHashes remoteFileHashes = mock(RemoteFileHashes.class);
-
-  @Test
-  public void hash() throws Exception {
-    File file = temp.newFile();
-    FileUtils.write(file, "fooo");
-
-    FileHashes hashes = new FileHashes(remoteFileHashes);
-    assertThat(hashes.hash(file, Charset.forName("UTF-8"))).isEqualTo("efc4470c96a94b1ff400175ef8368444");
-    verifyZeroInteractions(remoteFileHashes);
-  }
-
-  @Test
-  public void remote_hash() throws Exception {
-    String path = "src/main/java/Foo.java";
-    when(remoteFileHashes.remoteHash(path)).thenReturn("ABCDE");
-
-    FileHashes hashes = new FileHashes(remoteFileHashes);
-    assertThat(hashes.remoteHash(path)).isEqualTo("ABCDE");
-  }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileIndexTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileIndexTest.java
deleted file mode 100644 (file)
index b0c6ed5..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.resources.Project;
-import org.sonar.api.scan.filesystem.InputDir;
-import org.sonar.api.scan.filesystem.InputFile;
-import org.sonar.api.scan.filesystem.PathResolver;
-import org.sonar.api.scan.filesystem.internal.DefaultInputDir;
-
-import java.io.File;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class FileIndexTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Test
-  public void should_return_inputDir() throws Exception {
-    FileIndex index = new FileIndex(null, null, null, null, null, new PathResolver(), new Project("myProject"));
-    File baseDir = temp.newFolder();
-    DefaultModuleFileSystem fileSystem = mock(DefaultModuleFileSystem.class);
-    when(fileSystem.baseDir()).thenReturn(baseDir);
-    File ioFile = new File(baseDir, "src/main/java/com/foo");
-    InputDir inputDir = index.inputDir(fileSystem, ioFile);
-
-    assertThat(inputDir.name()).isEqualTo("src/main/java/com/foo");
-    assertThat(inputDir.file()).isEqualTo(ioFile);
-    assertThat(inputDir.attribute(DefaultInputDir.ATTRIBUTE_COMPONENT_KEY)).isEqualTo("myProject:src/main/java/com/foo");
-  }
-
-  @Test
-  public void should_not_index_aggregator() throws Exception {
-    Project project = new Project("myProject");
-    new Project("moduleA").setParent(project);
-    InputFileCache fileCache = mock(InputFileCache.class);
-    FileIndex index = new FileIndex(null, null, null, fileCache, null, new PathResolver(), project);
-
-    index.index(null);
-
-    verify(fileCache, never()).put(anyString(), any(InputFile.class));
-  }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileMetadataTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/FileMetadataTest.java
new file mode 100644 (file)
index 0000000..b0a4703
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import com.google.common.base.Charsets;
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class FileMetadataTest {
+
+  private static final String EXPECTED_HASH_WITHOUT_LATEST_EOL = "c80cc50d65ace6c4eb63f189d274dbeb";
+  private static final String EXPECTED_HASH_WITH_LATEST_EOL = "bf77e51d219e7d7d643faac86f1b5d15";
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void windows_without_latest_eol() throws Exception {
+    File tempFile = temp.newFile();
+    FileUtils.write(tempFile, "foo\r\nbar\r\nbaz", Charsets.UTF_8, true);
+
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(tempFile, Charsets.UTF_8);
+    assertThat(metadata.lines).isEqualTo(3L);
+    assertThat(metadata.hash).isEqualTo(EXPECTED_HASH_WITHOUT_LATEST_EOL);
+  }
+
+  @Test
+  public void windows_with_latest_eol() throws Exception {
+    File tempFile = temp.newFile();
+    FileUtils.write(tempFile, "foo\r\nbar\r\nbaz\r\n", Charsets.UTF_8, true);
+
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(tempFile, Charsets.UTF_8);
+    assertThat(metadata.lines).isEqualTo(3L);
+    assertThat(metadata.hash).isEqualTo(EXPECTED_HASH_WITH_LATEST_EOL);
+  }
+
+  @Test
+  public void unix_without_latest_eol() throws Exception {
+    File tempFile = temp.newFile();
+    FileUtils.write(tempFile, "foo\nbar\nbaz", Charsets.UTF_8, true);
+
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(tempFile, Charsets.UTF_8);
+    assertThat(metadata.lines).isEqualTo(3L);
+    assertThat(metadata.hash).isEqualTo(EXPECTED_HASH_WITHOUT_LATEST_EOL);
+  }
+
+  @Test
+  public void unix_with_latest_eol() throws Exception {
+    File tempFile = temp.newFile();
+    FileUtils.write(tempFile, "foo\nbar\nbaz\n", Charsets.UTF_8, true);
+
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(tempFile, Charsets.UTF_8);
+    assertThat(metadata.lines).isEqualTo(3L);
+    assertThat(metadata.hash).isEqualTo(EXPECTED_HASH_WITH_LATEST_EOL);
+  }
+
+  @Test
+  public void mix_of_newlines_with_latest_eol() throws Exception {
+    File tempFile = temp.newFile();
+    FileUtils.write(tempFile, "foo\nbar\r\nbaz\n", Charsets.UTF_8, true);
+
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(tempFile, Charsets.UTF_8);
+    assertThat(metadata.lines).isEqualTo(3L);
+    assertThat(metadata.hash).isEqualTo(EXPECTED_HASH_WITH_LATEST_EOL);
+  }
+
+  @Test
+  public void mix_of_newlines_without_latest_eol() throws Exception {
+    File tempFile = temp.newFile();
+    FileUtils.write(tempFile, "foo\nbar\r\nbaz", Charsets.UTF_8, true);
+
+    FileMetadata.Metadata metadata = FileMetadata.INSTANCE.read(tempFile, Charsets.UTF_8);
+    assertThat(metadata.lines).isEqualTo(3L);
+    assertThat(metadata.hash).isEqualTo(EXPECTED_HASH_WITHOUT_LATEST_EOL);
+  }
+
+  @Test
+  public void should_throw_if_file_does_not_exist() throws Exception {
+    File tempFolder = temp.newFolder();
+    File file = new File(tempFolder, "doesNotExist.txt");
+
+    thrown.expect(IllegalStateException.class);
+    thrown.expectMessage("Fail to read file '" + file.getAbsolutePath() + "' with encoding 'UTF-8'");
+
+    FileMetadata.INSTANCE.read(file, Charsets.UTF_8);
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageDetectionTest.java
new file mode 100644 (file)
index 0000000..4f45515
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import com.google.common.base.Charsets;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.Settings;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Languages;
+import org.sonar.api.scan.filesystem.InputFile;
+import org.sonar.api.scan.filesystem.internal.InputFileBuilder;
+import org.sonar.api.utils.MessageException;
+
+import java.io.File;
+import java.io.IOException;
+
+import static junit.framework.Assert.fail;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.spy;
+
+public class LanguageDetectionTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void test_sanitizeExtension() throws Exception {
+    assertThat(LanguageDetection.sanitizeExtension(".cbl")).isEqualTo("cbl");
+    assertThat(LanguageDetection.sanitizeExtension(".CBL")).isEqualTo("cbl");
+    assertThat(LanguageDetection.sanitizeExtension("CBL")).isEqualTo("cbl");
+    assertThat(LanguageDetection.sanitizeExtension("cbl")).isEqualTo("cbl");
+  }
+
+  @Test
+  public void search_by_file_extension() throws Exception {
+    Languages languages = new Languages(new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob"));
+    LanguageDetection detection = new LanguageDetection(new Settings(), languages);
+
+    assertThat(detection.language(newInputFile("Foo.java"))).isEqualTo("java");
+    assertThat(detection.language(newInputFile("src/Foo.java"))).isEqualTo("java");
+    assertThat(detection.language(newInputFile("Foo.JAVA"))).isEqualTo("java");
+    assertThat(detection.language(newInputFile("Foo.jav"))).isEqualTo("java");
+    assertThat(detection.language(newInputFile("Foo.Jav"))).isEqualTo("java");
+
+    assertThat(detection.language(newInputFile("abc.cbl"))).isEqualTo("cobol");
+    assertThat(detection.language(newInputFile("abc.CBL"))).isEqualTo("cobol");
+
+    assertThat(detection.language(newInputFile("abc.php"))).isNull();
+    assertThat(detection.language(newInputFile("abc"))).isNull();
+  }
+
+  @Test
+  public void should_not_fail_if_no_language() throws Exception {
+    LanguageDetection detection = spy(new LanguageDetection(new Settings(), new Languages()));
+    assertThat(detection.language(newInputFile("Foo.java"))).isNull();
+  }
+
+  @Test
+  public void plugin_can_declare_a_file_extension_twice_for_case_sensitivity() throws Exception {
+    Languages languages = new Languages(new MockLanguage("abap", "abap", "ABAP"));
+
+    LanguageDetection detection = new LanguageDetection(new Settings(), languages);
+    assertThat(detection.language(newInputFile("abc.abap"))).isEqualTo("abap");
+  }
+
+  @Test
+  public void language_with_no_extension() throws Exception {
+    // abap does not declare any file extensions.
+    // When analyzing an ABAP project, then all source files must be parsed.
+    Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("abap"));
+
+    // No side-effect on non-ABAP projects
+    LanguageDetection detection = new LanguageDetection(new Settings(), languages);
+    assertThat(detection.language(newInputFile("abc"))).isNull();
+    assertThat(detection.language(newInputFile("abc.abap"))).isNull();
+    assertThat(detection.language(newInputFile("abc.java"))).isEqualTo("java");
+
+    Settings settings = new Settings();
+    settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "abap");
+    detection = new LanguageDetection(settings, languages);
+    assertThat(detection.language(newInputFile("abc"))).isEqualTo("abap");
+    assertThat(detection.language(newInputFile("abc.txt"))).isEqualTo("abap");
+    assertThat(detection.language(newInputFile("abc.java"))).isEqualTo("abap");
+  }
+
+  @Test
+  public void force_language_using_deprecated_property() throws Exception {
+    Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"));
+
+    Settings settings = new Settings();
+    settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "java");
+    LanguageDetection detection = new LanguageDetection(settings, languages);
+    assertThat(detection.language(newInputFile("abc"))).isNull();
+    assertThat(detection.language(newInputFile("abc.php"))).isNull();
+    assertThat(detection.language(newInputFile("abc.java"))).isEqualTo("java");
+    assertThat(detection.language(newInputFile("src/abc.java"))).isEqualTo("java");
+  }
+
+  @Test
+  public void fail_if_invalid_language() throws Exception {
+    thrown.expect(MessageException.class);
+    thrown.expectMessage("No language is installed with key 'unknown'. Please update property 'sonar.language'");
+
+    Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"));
+    Settings settings = new Settings();
+    settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "unknown");
+    new LanguageDetection(settings, languages);
+  }
+
+  @Test
+  public void fail_if_conflicting_language_suffix() throws Exception {
+    Languages languages = new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"));
+    LanguageDetection detection = new LanguageDetection(new Settings(), languages);
+    try {
+      detection.language(newInputFile("abc.xhtml"));
+      fail();
+    } catch (MessageException e) {
+      assertThat(e.getMessage())
+        .contains("Language of file 'abc.xhtml' can not be decided as the file matches patterns of both ")
+        .contains("sonar.lang.patterns.web : **/*.xhtml")
+        .contains("sonar.lang.patterns.xml : **/*.xhtml");
+    }
+  }
+
+  @Test
+  public void solve_conflict_using_filepattern() throws Exception {
+    Languages languages = new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"));
+
+    Settings settings = new Settings();
+    settings.setProperty("sonar.lang.patterns.xml", "xml/**");
+    settings.setProperty("sonar.lang.patterns.web", "web/**");
+    LanguageDetection detection = new LanguageDetection(settings, languages);
+    assertThat(detection.language(newInputFile("xml/abc.xhtml"))).isEqualTo("xml");
+    assertThat(detection.language(newInputFile("web/abc.xhtml"))).isEqualTo("web");
+  }
+
+  @Test
+  public void fail_if_conflicting_filepattern() throws Exception {
+    Languages languages = new Languages(new MockLanguage("abap", "abap"), new MockLanguage("cobol", "cobol"));
+    Settings settings = new Settings();
+    settings.setProperty("sonar.lang.patterns.abap", "*.abap,*.txt");
+    settings.setProperty("sonar.lang.patterns.cobol", "*.cobol,*.txt");
+
+    LanguageDetection detection = new LanguageDetection(settings, languages);
+
+    assertThat(detection.language(newInputFile("abc.abap"))).isEqualTo("abap");
+    assertThat(detection.language(newInputFile("abc.cobol"))).isEqualTo("cobol");
+    try {
+      detection.language(newInputFile("abc.txt"));
+      fail();
+    } catch (MessageException e) {
+      assertThat(e.getMessage())
+        .contains("Language of file 'abc.txt' can not be decided as the file matches patterns of both ")
+        .contains("sonar.lang.patterns.abap : *.abap,*.txt")
+        .contains("sonar.lang.patterns.cobol : *.cobol,*.txt");
+    }
+  }
+
+  private InputFile newInputFile(String path) throws IOException {
+    File basedir = temp.newFolder();
+    return new InputFileBuilder(new File(basedir, path), Charsets.UTF_8, path).build();
+  }
+
+  static class MockLanguage implements Language {
+    private final String key;
+    private final String[] extensions;
+
+    MockLanguage(String key, String... extensions) {
+      this.key = key;
+      this.extensions = extensions;
+    }
+
+    @Override
+    public String getKey() {
+      return key;
+    }
+
+    @Override
+    public String getName() {
+      return key;
+    }
+
+    @Override
+    public String[] getFileSuffixes() {
+      return extensions;
+    }
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/LanguageRecognizerTest.java
deleted file mode 100644 (file)
index 70984dc..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import com.google.common.base.Charsets;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.Settings;
-import org.sonar.api.resources.Language;
-import org.sonar.api.resources.Languages;
-import org.sonar.api.scan.filesystem.InputFile;
-import org.sonar.api.scan.filesystem.internal.InputFileBuilder;
-import org.sonar.api.utils.SonarException;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.spy;
-
-public class LanguageRecognizerTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Test
-  public void test_sanitizeExtension() throws Exception {
-    assertThat(LanguageRecognizer.sanitizeExtension(".cbl")).isEqualTo("cbl");
-    assertThat(LanguageRecognizer.sanitizeExtension(".CBL")).isEqualTo("cbl");
-    assertThat(LanguageRecognizer.sanitizeExtension("CBL")).isEqualTo("cbl");
-    assertThat(LanguageRecognizer.sanitizeExtension("cbl")).isEqualTo("cbl");
-  }
-
-  @Test
-  public void search_by_file_extension() throws Exception {
-    Languages languages = new Languages(new MockLanguage("java", "java", "jav"), new MockLanguage("cobol", "cbl", "cob"));
-    LanguageRecognizer recognizer = new LanguageRecognizer(new Settings(), languages);
-
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("Foo.java"))).isEqualTo("java");
-    assertThat(recognizer.of(newInputFile("src/Foo.java"))).isEqualTo("java");
-    assertThat(recognizer.of(newInputFile("Foo.JAVA"))).isEqualTo("java");
-    assertThat(recognizer.of(newInputFile("Foo.jav"))).isEqualTo("java");
-    assertThat(recognizer.of(newInputFile("Foo.Jav"))).isEqualTo("java");
-
-    assertThat(recognizer.of(newInputFile("abc.cbl"))).isEqualTo("cobol");
-    assertThat(recognizer.of(newInputFile("abc.CBL"))).isEqualTo("cobol");
-
-    assertThat(recognizer.of(newInputFile("abc.php"))).isNull();
-    assertThat(recognizer.of(newInputFile("abc"))).isNull();
-    recognizer.stop();
-  }
-
-  @Test
-  public void should_not_fail_if_no_language() throws Exception {
-    LanguageRecognizer recognizer = spy(new LanguageRecognizer(new Settings(), new Languages()));
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("Foo.java"))).isNull();
-  }
-
-  @Test
-  public void plugin_can_declare_a_file_extension_twice_for_case_sensitivity() throws Exception {
-    Languages languages = new Languages(new MockLanguage("abap", "abap", "ABAP"));
-
-    LanguageRecognizer recognizer = new LanguageRecognizer(new Settings(), languages);
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("abc.abap"))).isEqualTo("abap");
-  }
-
-  @Test
-  public void language_with_no_extension() throws Exception {
-    // abap does not declare any file extensions.
-    // When analyzing an ABAP project, then all source files must be parsed.
-    Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("abap"));
-
-    // No side-effect on non-ABAP projects
-    LanguageRecognizer recognizer = new LanguageRecognizer(new Settings(), languages);
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("abc"))).isNull();
-    assertThat(recognizer.of(newInputFile("abc.abap"))).isNull();
-    assertThat(recognizer.of(newInputFile("abc.java"))).isEqualTo("java");
-    recognizer.stop();
-
-    Settings settings = new Settings();
-    settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "abap");
-    recognizer = new LanguageRecognizer(settings, languages);
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("abc"))).isEqualTo("abap");
-    assertThat(recognizer.of(newInputFile("abc.txt"))).isEqualTo("abap");
-    assertThat(recognizer.of(newInputFile("abc.java"))).isEqualTo("abap");
-    recognizer.stop();
-  }
-
-  @Test
-  public void force_language_using_deprecated_property() throws Exception {
-    Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"));
-
-    Settings settings = new Settings();
-    settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "java");
-    LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("abc"))).isNull();
-    assertThat(recognizer.of(newInputFile("abc.php"))).isNull();
-    assertThat(recognizer.of(newInputFile("abc.java"))).isEqualTo("java");
-    assertThat(recognizer.of(newInputFile("src/abc.java"))).isEqualTo("java");
-    recognizer.stop();
-  }
-
-  @Test
-  public void fail_if_invalid_language() throws Exception {
-    Languages languages = new Languages(new MockLanguage("java", "java"), new MockLanguage("php", "php"));
-
-    Settings settings = new Settings();
-    settings.setProperty(CoreProperties.PROJECT_LANGUAGE_PROPERTY, "unknow");
-    LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
-    recognizer.start();
-    thrown.expect(SonarException.class);
-    thrown.expectMessage("No language is installed with key 'unknow'. Please update property 'sonar.language'");
-    recognizer.of(newInputFile("abc"));
-    recognizer.stop();
-  }
-
-  @Test
-  public void fail_if_conflicting_language_suffix() throws Exception {
-    Languages languages = new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"));
-
-    Settings settings = new Settings();
-    LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
-    recognizer.start();
-    thrown.expect(SonarException.class);
-    thrown.expectMessage(new BaseMatcher<String>() {
-      @Override
-      public void describeTo(Description arg0) {
-      }
-
-      @Override
-      public boolean matches(Object arg0) {
-        // Need custom matcher because order of language in the exception is not deterministic (hashmap)
-        return arg0.toString().contains("Language of file 'abc.xhtml' can not be decided as the file matches patterns of both ")
-          && arg0.toString().contains("sonar.lang.patterns.web : **/*.xhtml")
-          && arg0.toString().contains("sonar.lang.patterns.xml : **/*.xhtml");
-      }
-    });
-    recognizer.of(newInputFile("abc.xhtml"));
-    recognizer.stop();
-  }
-
-  @Test
-  public void solve_conflict_using_filepattern() throws Exception {
-    Languages languages = new Languages(new MockLanguage("xml", "xhtml"), new MockLanguage("web", "xhtml"));
-
-    Settings settings = new Settings();
-    settings.setProperty("sonar.lang.patterns.xml", "xml/**");
-    settings.setProperty("sonar.lang.patterns.web", "web/**");
-    LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("xml/abc.xhtml"))).isEqualTo("xml");
-    assertThat(recognizer.of(newInputFile("web/abc.xhtml"))).isEqualTo("web");
-    recognizer.stop();
-  }
-
-  @Test
-  public void fail_if_conflicting_filepattern() throws Exception {
-    Languages languages = new Languages(new MockLanguage("abap", "abap"), new MockLanguage("cobol", "cobol"));
-
-    Settings settings = new Settings();
-    settings.setProperty("sonar.lang.patterns.abap", "*.abap,*.txt");
-    settings.setProperty("sonar.lang.patterns.cobol", "*.cobol,*.txt");
-    LanguageRecognizer recognizer = new LanguageRecognizer(settings, languages);
-    recognizer.start();
-    assertThat(recognizer.of(newInputFile("abc.abap"))).isEqualTo("abap");
-    assertThat(recognizer.of(newInputFile("abc.cobol"))).isEqualTo("cobol");
-    thrown.expect(SonarException.class);
-    thrown.expectMessage(new BaseMatcher<String>() {
-      @Override
-      public void describeTo(Description arg0) {
-      }
-
-      @Override
-      public boolean matches(Object arg0) {
-        // Need custom matcher because order of language in the exception is not deterministic (hashmap)
-        return arg0.toString().contains("Language of file 'abc.txt' can not be decided as the file matches patterns of both ")
-          && arg0.toString().contains("sonar.lang.patterns.abap : *.abap,*.txt")
-          && arg0.toString().contains("sonar.lang.patterns.cobol : *.cobol,*.txt");
-      }
-    });
-    recognizer.of(newInputFile("abc.txt"));
-    recognizer.stop();
-  }
-
-  private InputFile newInputFile(String path) throws IOException {
-    File basedir = temp.newFolder();
-    return new InputFileBuilder(new File(basedir, path), Charsets.UTF_8, path).build();
-  }
-
-  static class MockLanguage implements Language {
-    private final String key;
-    private final String[] extensions;
-
-    MockLanguage(String key, String... extensions) {
-      this.key = key;
-      this.extensions = extensions;
-    }
-
-    @Override
-    public String getKey() {
-      return key;
-    }
-
-    @Override
-    public String getName() {
-      return key;
-    }
-
-    @Override
-    public String[] getFileSuffixes() {
-      return extensions;
-    }
-  }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/PreviousFileHashesLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/PreviousFileHashesLoaderTest.java
new file mode 100644 (file)
index 0000000..6b4e518
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.batch.scan.filesystem;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.database.model.Snapshot;
+import org.sonar.batch.components.PastSnapshot;
+import org.sonar.batch.components.PastSnapshotFinder;
+import org.sonar.core.source.SnapshotDataTypes;
+import org.sonar.core.source.db.SnapshotDataDao;
+import org.sonar.core.source.db.SnapshotDataDto;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PreviousFileHashesLoaderTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  PastSnapshotFinder pastSnapshotFinder = mock(PastSnapshotFinder.class);
+  Snapshot snapshot = mock(Snapshot.class);
+  SnapshotDataDao snapshotDataDao = mock(SnapshotDataDao.class);
+  PreviousFileHashLoader loader = new PreviousFileHashLoader(snapshot, snapshotDataDao, pastSnapshotFinder);
+
+  @Test
+  public void should_return_null_if_no_previous_snapshot() throws Exception {
+    when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(new PastSnapshot("foo"));
+
+    Map<String, String> hashByRelativePath = loader.hashByRelativePath();
+    assertThat(hashByRelativePath.get("src/main/java/foo/Bar.java")).isNull();
+  }
+
+  @Test
+  public void should_return_null_if_no_remote_hashes() throws Exception {
+    Snapshot previousSnapshot = mock(Snapshot.class);
+    PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
+    when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
+
+    Map<String, String> hashByRelativePath = loader.hashByRelativePath();
+    assertThat(hashByRelativePath.get("src/main/java/foo/Bar.java")).isNull();
+  }
+
+  @Test
+  public void should_return_remote_hash() throws Exception {
+    Snapshot previousSnapshot = mock(Snapshot.class);
+    when(previousSnapshot.getId()).thenReturn(123);
+    PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
+    when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
+
+    SnapshotDataDto snapshotDataDto = new SnapshotDataDto();
+    snapshotDataDto.setData("src/main/java/foo/Bar.java=abcd1234");
+    when(snapshotDataDao.selectSnapshotData(123, Arrays.asList(SnapshotDataTypes.FILE_HASHES)))
+      .thenReturn(Arrays.asList(snapshotDataDto));
+
+    Map<String, String> hashByRelativePath = loader.hashByRelativePath();
+    assertThat(hashByRelativePath.get("src/main/java/foo/Bar.java")).isEqualTo("abcd1234");
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/RemoteFileHashesTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/RemoteFileHashesTest.java
deleted file mode 100644 (file)
index 1212f99..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.batch.scan.filesystem;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.database.model.Snapshot;
-import org.sonar.batch.components.PastSnapshot;
-import org.sonar.batch.components.PastSnapshotFinder;
-import org.sonar.core.source.SnapshotDataTypes;
-import org.sonar.core.source.db.SnapshotDataDao;
-import org.sonar.core.source.db.SnapshotDataDto;
-
-import java.util.Arrays;
-import java.util.Date;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class RemoteFileHashesTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  PastSnapshotFinder pastSnapshotFinder = mock(PastSnapshotFinder.class);
-  Snapshot snapshot = mock(Snapshot.class);
-  SnapshotDataDao snapshotDataDao = mock(SnapshotDataDao.class);
-  RemoteFileHashes hashes = new RemoteFileHashes(snapshot, snapshotDataDao, pastSnapshotFinder);
-
-  @Test
-  public void should_return_null_if_no_remote_snapshot() throws Exception {
-    when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(new PastSnapshot("foo"));
-
-    hashes.start();
-    assertThat(hashes.remoteHash("src/main/java/foo/Bar.java")).isNull();
-    hashes.stop();
-  }
-
-  @Test
-  public void should_return_null_if_no_remote_hashes() throws Exception {
-    Snapshot previousSnapshot = mock(Snapshot.class);
-    PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
-    when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
-
-    hashes.start();
-    assertThat(hashes.remoteHash("src/main/java/foo/Bar.java")).isNull();
-    hashes.stop();
-  }
-
-  @Test
-  public void should_return_remote_hash() throws Exception {
-    Snapshot previousSnapshot = mock(Snapshot.class);
-    when(previousSnapshot.getId()).thenReturn(123);
-    PastSnapshot pastSnapshot = new PastSnapshot("foo", new Date(), previousSnapshot);
-    when(pastSnapshotFinder.findPreviousAnalysis(snapshot)).thenReturn(pastSnapshot);
-
-    SnapshotDataDto snapshotDataDto = new SnapshotDataDto();
-    snapshotDataDto.setData("src/main/java/foo/Bar.java=abcd1234");
-    when(snapshotDataDao.selectSnapshotData(123, Arrays.asList(SnapshotDataTypes.FILE_HASHES)))
-      .thenReturn(Arrays.asList(snapshotDataDto));
-
-    hashes.start();
-    assertThat(hashes.remoteHash("src/main/java/foo/Bar.java")).isEqualTo("abcd1234");
-    hashes.stop();
-  }
-}
index 2e83b122d0263ca2902af413f4cc9ff479df9e34..3789f0c5759fca616f918a2b996530bc9c4dd77e 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.api.scan.filesystem.internal;
 
+import com.google.common.collect.Maps;
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.scan.filesystem.InputDir;
@@ -51,6 +52,12 @@ public class DefaultInputDir implements InputDir {
     this.attributes = attributes;
   }
 
+  public DefaultInputDir(String absolutePath, String path) {
+    this.absolutePath = absolutePath;
+    this.path = path;
+    this.attributes = Maps.newHashMap();
+  }
+
   /**
    * Plugins must not build their own instances of {@link InputDir}.
    * {@link org.sonar.api.scan.filesystem.ModuleFileSystem} must be used to search for inputDir.
@@ -116,4 +123,9 @@ public class DefaultInputDir implements InputDir {
   public String toString() {
     return String.format("[%s]", path);
   }
+
+  public DefaultInputDir setKey(String s) {
+    attributes.put(ATTRIBUTE_COMPONENT_KEY, s);
+    return this;
+  }
 }
index e8ea24e1715824763c4c616102445a24192473e5..8509d14e329d561ff0138c9d9334b37749afa742 100644 (file)
@@ -26,7 +26,6 @@ import org.sonar.api.scan.filesystem.InputFile;
 import org.sonar.api.utils.PathUtils;
 
 import javax.annotation.CheckForNull;
-
 import java.io.File;
 import java.nio.charset.Charset;
 import java.util.Map;
@@ -67,6 +66,7 @@ public class DefaultInputFile implements InputFile {
   private final String path;
   private final Map<String, String> attributes;
   private final String encoding;
+  private long lines = 0L;
 
   private DefaultInputFile(File file, Charset encoding, String path, Map<String, String> attributes) {
     this.encoding = encoding.name();
@@ -148,6 +148,69 @@ public class DefaultInputFile implements InputFile {
     return absolutePath.hashCode();
   }
 
+  public DefaultInputFile setLines(long l) {
+    this.lines = l;
+    return this;
+  }
+
+  public String language() {
+    return attributes.get(ATTRIBUTE_LANGUAGE);
+  }
+
+  public DefaultInputFile setLanguage(String s) {
+    attributes.put(ATTRIBUTE_LANGUAGE, s);
+    return this;
+  }
+
+  public DefaultInputFile setHash(String s) {
+    attributes.put(ATTRIBUTE_HASH, s);
+    return this;
+  }
+
+  public DefaultInputFile setStatus(String s) {
+    attributes.put(ATTRIBUTE_STATUS, s);
+    return this;
+  }
+
+  public DefaultInputFile setKey(String s) {
+    attributes.put(ATTRIBUTE_COMPONENT_KEY, s);
+    return this;
+  }
+
+  public DefaultInputFile setDeprecatedKey(String s) {
+    attributes.put(ATTRIBUTE_COMPONENT_DEPRECATED_KEY, s);
+    return this;
+  }
+
+  public DefaultInputFile setType(String s) {
+    attributes.put(ATTRIBUTE_TYPE, s);
+    return this;
+  }
+
+  /**
+   * Used only for backward-compatibility. Meaningless since version 4.2.
+   */
+  public String sourceDirAbsolutePath() {
+    return attributes.get(ATTRIBUTE_SOURCEDIR_PATH);
+  }
+
+  public DefaultInputFile setSourceDirAbsolutePath(String s) {
+    attributes.put(ATTRIBUTE_SOURCEDIR_PATH, FilenameUtils.normalize(s, true));
+    return this;
+  }
+
+  /**
+   * Used only for backward-compatibility. Meaningless since version 4.2.
+   */
+  public String pathRelativeToSourceDir() {
+    return attributes.get(ATTRIBUTE_SOURCE_RELATIVE_PATH);
+  }
+
+  public DefaultInputFile setPathRelativeToSourceDir(String s) {
+    attributes.put(ATTRIBUTE_SOURCE_RELATIVE_PATH, FilenameUtils.normalize(s, true));
+    return this;
+  }
+
   @Override
   public String toString() {
     return String.format("[%s,%s]", path, type());