]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11459 Remove ModuleFileSystemInitializer
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 15 Nov 2018 17:52:56 +0000 (18:52 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:01 +0000 (09:43 +0100)
22 files changed:
sonar-core/src/main/java/org/sonar/core/config/MultivalueProperty.java [deleted file]
sonar-core/src/test/java/org/sonar/core/config/MultivaluePropertyTest.java [deleted file]
sonar-plugin-api/build.gradle
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/AbstractProjectOrModule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputModule.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputProject.java
sonar-plugin-api/src/main/java/org/sonar/api/config/internal/MultivalueProperty.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/scan/filesystem/FileExclusions.java
sonar-plugin-api/src/test/java/org/sonar/api/config/internal/MultivaluePropertyTest.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/config/DefaultConfiguration.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/InputModuleHierarchyProvider.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/InputProjectProvider.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ModuleScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectReactorBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DefaultModuleFileSystem.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/InputFileBuilder.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/LanguageDetection.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializer.java [deleted file]
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/InputFileBuilderTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializerTest.java [deleted file]

diff --git a/sonar-core/src/main/java/org/sonar/core/config/MultivalueProperty.java b/sonar-core/src/main/java/org/sonar/core/config/MultivalueProperty.java
deleted file mode 100644 (file)
index c4cf1d3..0000000
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.config;
-
-import com.google.common.annotations.VisibleForTesting;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.function.Function;
-import org.apache.commons.csv.CSVFormat;
-import org.apache.commons.csv.CSVParser;
-import org.apache.commons.csv.CSVRecord;
-import org.apache.commons.lang.ArrayUtils;
-
-public class MultivalueProperty {
-  private MultivalueProperty() {
-    // prevents instantiation
-  }
-
-  public static String[] parseAsCsv(String key, String value) {
-    return parseAsCsv(key, value, Function.identity());
-  }
-
-  public static String[] parseAsCsv(String key, String value, Function<String, String> valueProcessor) {
-    String cleanValue = MultivalueProperty.trimFieldsAndRemoveEmptyFields(value);
-    List<String> result = new ArrayList<>();
-    try (CSVParser csvParser = CSVFormat.RFC4180
-      .withHeader((String) null)
-      .withIgnoreEmptyLines()
-      .withIgnoreSurroundingSpaces()
-      .parse(new StringReader(cleanValue))) {
-      List<CSVRecord> records = csvParser.getRecords();
-      if (records.isEmpty()) {
-        return ArrayUtils.EMPTY_STRING_ARRAY;
-      }
-      processRecords(result, records, valueProcessor);
-      return result.toArray(new String[result.size()]);
-    } catch (IOException e) {
-      throw new IllegalStateException("Property: '" + key + "' doesn't contain a valid CSV value: '" + value + "'", e);
-    }
-  }
-
-  /**
-   * In most cases we expect a single record. <br>Having multiple records means the input value was splitted over multiple lines (this is common in Maven).
-   * For example:
-   * <pre>
-   *   &lt;sonar.exclusions&gt;
-   *     src/foo,
-   *     src/bar,
-   *     src/biz
-   *   &lt;sonar.exclusions&gt;
-   * </pre>
-   * In this case records will be merged to form a single list of items. Last item of a record is appended to first item of next record.
-   * <p>
-   * This is a very curious case, but we try to preserve line break in the middle of an item:
-   * <pre>
-   *   &lt;sonar.exclusions&gt;
-   *     a
-   *     b,
-   *     c
-   *   &lt;sonar.exclusions&gt;
-   * </pre>
-   * will produce ['a\nb', 'c']
-   */
-  private static void processRecords(List<String> result, List<CSVRecord> records, Function<String, String> valueProcessor) {
-    for (CSVRecord csvRecord : records) {
-      Iterator<String> it = csvRecord.iterator();
-      if (!result.isEmpty()) {
-        String next = it.next();
-        if (!next.isEmpty()) {
-          int lastItemIdx = result.size() - 1;
-          String previous = result.get(lastItemIdx);
-          if (previous.isEmpty()) {
-            result.set(lastItemIdx, valueProcessor.apply(next));
-          } else {
-            result.set(lastItemIdx, valueProcessor.apply(previous + "\n" + next));
-          }
-        }
-      }
-      it.forEachRemaining(s -> {
-        String apply = valueProcessor.apply(s);
-        result.add(apply);
-      });
-    }
-  }
-
-  /**
-   * Removes the empty fields from the value of a multi-value property from empty fields, including trimming each field.
-   * <p>
-   * Quotes can be used to prevent an empty field to be removed (as it is used to preserve empty spaces).
-   * <ul>
-   *    <li>{@code "" => ""}</li>
-   *    <li>{@code " " => ""}</li>
-   *    <li>{@code "," => ""}</li>
-   *    <li>{@code ",," => ""}</li>
-   *    <li>{@code ",,," => ""}</li>
-   *    <li>{@code ",a" => "a"}</li>
-   *    <li>{@code "a," => "a"}</li>
-   *    <li>{@code ",a," => "a"}</li>
-   *    <li>{@code "a,,b" => "a,b"}</li>
-   *    <li>{@code "a,   ,b" => "a,b"}</li>
-   *    <li>{@code "a,\"\",b" => "a,b"}</li>
-   *    <li>{@code "\"a\",\"b\"" => "\"a\",\"b\""}</li>
-   *    <li>{@code "\"  a  \",\"b \"" => "\"  a  \",\"b \""}</li>
-   *    <li>{@code "\"a\",\"\",\"b\"" => "\"a\",\"\",\"b\""}</li>
-   *    <li>{@code "\"a\",\"  \",\"b\"" => "\"a\",\"  \",\"b\""}</li>
-   *    <li>{@code "\"  a,,b,c  \",\"d \"" => "\"  a,,b,c  \",\"d \""}</li>
-   *    <li>{@code "a,\"  \",b" => "ab"]}</li>
-   * </ul>
-   */
-  @VisibleForTesting
-  static String trimFieldsAndRemoveEmptyFields(String str) {
-    char[] chars = str.toCharArray();
-    char[] res = new char[chars.length];
-    /*
-     * set when reading the first non trimmable char after a separator char (or the beginning of the string)
-     * unset when reading a separator
-     */
-    boolean inField = false;
-    boolean inQuotes = false;
-    int i = 0;
-    int resI = 0;
-    for (; i < chars.length; i++) {
-      boolean isSeparator = chars[i] == ',';
-      if (!inQuotes && isSeparator) {
-        // exiting field (may already be unset)
-        inField = false;
-        if (resI > 0) {
-          resI = retroTrim(res, resI);
-        }
-      } else {
-        boolean isTrimmed = !inQuotes && istrimmable(chars[i]);
-        if (isTrimmed && !inField) {
-          // we haven't meet any non trimmable char since the last separator yet
-          continue;
-        }
-
-        boolean isEscape = isEscapeChar(chars[i]);
-        if (isEscape) {
-          inQuotes = !inQuotes;
-        }
-
-        // add separator as we already had one field
-        if (!inField && resI > 0) {
-          res[resI] = ',';
-          resI++;
-        }
-
-        // register in field (may already be set)
-        inField = true;
-        // copy current char
-        res[resI] = chars[i];
-        resI++;
-      }
-    }
-    // inQuotes can only be true at this point if quotes are unbalanced
-    if (!inQuotes) {
-      // trim end of str
-      resI = retroTrim(res, resI);
-    }
-    return new String(res, 0, resI);
-  }
-
-  private static boolean isEscapeChar(char aChar) {
-    return aChar == '"';
-  }
-
-  private static boolean istrimmable(char aChar) {
-    return aChar <= ' ';
-  }
-
-  /**
-   * Reads from index {@code resI} to the beginning into {@code res} looking up the location of the trimmable char with
-   * the lowest index before encountering a non-trimmable char.
-   * <p>
-   * This basically trims {@code res} from any trimmable char at its end.
-   *
-   * @return index of next location to put new char in res
-   */
-  private static int retroTrim(char[] res, int resI) {
-    int i = resI;
-    while (i >= 1) {
-      if (!istrimmable(res[i - 1])) {
-        return i;
-      }
-      i--;
-    }
-    return i;
-  }
-
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/config/MultivaluePropertyTest.java b/sonar-core/src/test/java/org/sonar/core/config/MultivaluePropertyTest.java
deleted file mode 100644 (file)
index fec15e4..0000000
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.config;
-
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Random;
-import java.util.function.Function;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.core.config.MultivalueProperty.parseAsCsv;
-import static org.sonar.core.config.MultivalueProperty.trimFieldsAndRemoveEmptyFields;
-
-@RunWith(DataProviderRunner.class)
-public class MultivaluePropertyTest {
-  private static final String[] EMPTY_STRING_ARRAY = {};
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  @Test
-  @UseDataProvider("testParseAsCsv")
-  public void parseAsCsv_for_coverage(String value, String[] expected) {
-    // parseAsCsv is extensively tested in org.sonar.server.config.ConfigurationProviderTest
-    assertThat(parseAsCsv("key", value))
-      .isEqualTo(parseAsCsv("key", value, Function.identity()))
-      .isEqualTo(expected);
-  }
-
-  @Test
-  public void parseAsCsv_fails_with_ISE_if_value_can_not_be_parsed() {
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("Property: 'multi' doesn't contain a valid CSV value: '\"a ,b'");
-
-    parseAsCsv("multi", "\"a ,b");
-  }
-
-  @DataProvider
-  public static Object[][] testParseAsCsv() {
-    return new Object[][] {
-      {"a", arrayOf("a")},
-      {" a", arrayOf("a")},
-      {"a ", arrayOf("a")},
-      {" a, b", arrayOf("a", "b")},
-      {"a,b ", arrayOf("a", "b")},
-      {"a,,,b,c,,d", arrayOf("a", "b", "c", "d")},
-      {" , \n ,, \t", EMPTY_STRING_ARRAY},
-      {"\" a\"", arrayOf(" a")},
-      {"\",\"", arrayOf(",")},
-      // escaped quote in quoted field
-      {"\"\"\"\"", arrayOf("\"")}
-    };
-  }
-
-  private static String[] arrayOf(String... strs) {
-    return strs;
-  }
-
-  @Test
-  public void trimFieldsAndRemoveEmptyFields_throws_NPE_if_arg_is_null() {
-    expectedException.expect(NullPointerException.class);
-
-    trimFieldsAndRemoveEmptyFields(null);
-  }
-
-  @Test
-  @UseDataProvider("plains")
-  public void trimFieldsAndRemoveEmptyFields_ignores_EmptyFields(String str) {
-    assertThat(trimFieldsAndRemoveEmptyFields("")).isEqualTo("");
-    assertThat(trimFieldsAndRemoveEmptyFields(str)).isEqualTo(str);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + str)).isEqualTo(str);
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ',')).isEqualTo(str);
-    assertThat(trimFieldsAndRemoveEmptyFields(",,," + str)).isEqualTo(str);
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,,")).isEqualTo(str);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str)).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,," + str)).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str)).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields("," + str + ",,," + str)).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(",,," + str + ",,," + str)).isEqualTo(str + ',' + str);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',')).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,," + str + ",")).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,," + str + ",,")).isEqualTo(str + ',' + str);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str + ',')).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(",," + str + ',' + str + ',')).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ",," + str + ',')).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str + ",,")).isEqualTo(str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(",,," + str + ",,," + str + ",,")).isEqualTo(str + ',' + str);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',' + str)).isEqualTo(str + ',' + str + ',' + str);
-    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',' + str)).isEqualTo(str + ',' + str + ',' + str);
-  }
-
-  @DataProvider
-  public static Object[][] plains() {
-    return new Object[][] {
-      {randomAlphanumeric(1)},
-      {randomAlphanumeric(2)},
-      {randomAlphanumeric(3 + new Random().nextInt(5))}
-    };
-  }
-
-  @Test
-  @UseDataProvider("emptyAndtrimmable")
-  public void trimFieldsAndRemoveEmptyFields_ignores_empty_fields_and_trims_fields(String empty, String trimmable) {
-    String expected = trimmable.trim();
-    assertThat(empty.trim()).isEmpty();
-
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + empty)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ",," + empty)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + trimmable)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(empty + ",," + trimmable)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + trimmable + ',' + empty)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(empty + ",," + trimmable + ",,," + empty)).isEqualTo(expected);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + empty + ',' + empty)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ",," + empty + ",,," + empty)).isEqualTo(expected);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + empty + ',' + trimmable)).isEqualTo(expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(empty + ",,,," + empty + ",," + trimmable)).isEqualTo(expected);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected + ',' + expected);
-    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + "," + trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected + ',' + expected);
-  }
-
-  @Test
-  public void trimAccordingToStringTrim() {
-    String str = randomAlphanumeric(4);
-    for (int i = 0; i <= ' '; i++) {
-      String prefixed = (char) i + str;
-      String suffixed = (char) i + str;
-      String both = (char) i + str + (char) i;
-      assertThat(trimFieldsAndRemoveEmptyFields(prefixed)).isEqualTo(prefixed.trim());
-      assertThat(trimFieldsAndRemoveEmptyFields(suffixed)).isEqualTo(suffixed.trim());
-      assertThat(trimFieldsAndRemoveEmptyFields(both)).isEqualTo(both.trim());
-    }
-  }
-
-  @DataProvider
-  public static Object[][] emptyAndtrimmable() {
-    Random random = new Random();
-    String oneEmpty = randomTrimmedChars(1, random);
-    String twoEmpty = randomTrimmedChars(2, random);
-    String threePlusEmpty = randomTrimmedChars(3 + random.nextInt(5), random);
-    String onePlusEmpty = randomTrimmedChars(1 + random.nextInt(5), random);
-
-    String plain = randomAlphanumeric(1);
-    String plainWithtrimmable = randomAlphanumeric(2) + onePlusEmpty + randomAlphanumeric(3);
-    String quotedWithSeparator = '"' + randomAlphanumeric(3) + ',' + randomAlphanumeric(2) + '"';
-    String quotedWithDoubleSeparator = '"' + randomAlphanumeric(3) + ",," + randomAlphanumeric(2) + '"';
-    String quotedWithtrimmable = '"' + randomAlphanumeric(3) + onePlusEmpty + randomAlphanumeric(2) + '"';
-
-    String[] empties = {oneEmpty, twoEmpty, threePlusEmpty};
-    String[] strings = {plain, plainWithtrimmable,
-      onePlusEmpty + plain, plain + onePlusEmpty, onePlusEmpty + plain + onePlusEmpty,
-      onePlusEmpty + plainWithtrimmable, plainWithtrimmable + onePlusEmpty, onePlusEmpty + plainWithtrimmable + onePlusEmpty,
-      onePlusEmpty + quotedWithSeparator, quotedWithSeparator + onePlusEmpty, onePlusEmpty + quotedWithSeparator + onePlusEmpty,
-      onePlusEmpty + quotedWithDoubleSeparator, quotedWithDoubleSeparator + onePlusEmpty, onePlusEmpty + quotedWithDoubleSeparator + onePlusEmpty,
-      onePlusEmpty + quotedWithtrimmable, quotedWithtrimmable + onePlusEmpty, onePlusEmpty + quotedWithtrimmable + onePlusEmpty
-    };
-
-    Object[][] res = new Object[empties.length * strings.length][2];
-    int i = 0;
-    for (String empty : empties) {
-      for (String string : strings) {
-        res[i][0] = empty;
-        res[i][1] = string;
-        i++;
-      }
-    }
-    return res;
-  }
-
-  @Test
-  @UseDataProvider("emptys")
-  public void trimFieldsAndRemoveEmptyFields_quotes_allow_to_preserve_fields(String empty) {
-    String quotedEmpty = '"' + empty + '"';
-
-    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty)).isEqualTo(quotedEmpty);
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + quotedEmpty)).isEqualTo(quotedEmpty);
-    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',')).isEqualTo(quotedEmpty);
-    assertThat(trimFieldsAndRemoveEmptyFields(',' + quotedEmpty + ',')).isEqualTo(quotedEmpty);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',' + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty);
-    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ",," + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty);
-
-    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',' + quotedEmpty + ',' + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty + ',' + quotedEmpty);
-  }
-
-  @DataProvider
-  public static Object[][] emptys() {
-    Random random = new Random();
-    return new Object[][] {
-      {randomTrimmedChars(1, random)},
-      {randomTrimmedChars(2, random)},
-      {randomTrimmedChars(3 + random.nextInt(5), random)}
-    };
-  }
-
-  @Test
-  public void trimFieldsAndRemoveEmptyFields_supports_escaped_quote_in_quotes() {
-    assertThat(trimFieldsAndRemoveEmptyFields("\"f\"\"oo\"")).isEqualTo("\"f\"\"oo\"");
-    assertThat(trimFieldsAndRemoveEmptyFields("\"f\"\"oo\",\"bar\"\"\"")).isEqualTo("\"f\"\"oo\",\"bar\"\"\"");
-  }
-
-  @Test
-  public void trimFieldsAndRemoveEmptyFields_does_not_fail_on_unbalanced_quotes() {
-    assertThat(trimFieldsAndRemoveEmptyFields("\"")).isEqualTo("\"");
-    assertThat(trimFieldsAndRemoveEmptyFields("\"foo")).isEqualTo("\"foo");
-    assertThat(trimFieldsAndRemoveEmptyFields("foo\"")).isEqualTo("foo\"");
-
-    assertThat(trimFieldsAndRemoveEmptyFields("\"foo\",\"")).isEqualTo("\"foo\",\"");
-    assertThat(trimFieldsAndRemoveEmptyFields("\",\"foo\"")).isEqualTo("\",\"foo\"");
-
-    assertThat(trimFieldsAndRemoveEmptyFields("\"foo\",\",  ")).isEqualTo("\"foo\",\",  ");
-
-    assertThat(trimFieldsAndRemoveEmptyFields(" a ,,b , c,  \"foo\",\"  ")).isEqualTo("a,b,c,\"foo\",\"  ");
-    assertThat(trimFieldsAndRemoveEmptyFields("\" a ,,b , c,  ")).isEqualTo("\" a ,,b , c,  ");
-  }
-
-  private static final char[] SOME_PRINTABLE_TRIMMABLE_CHARS = {
-    ' ', '\t', '\n', '\r'
-  };
-
-  /**
-   * Result of randomTrimmedChars being used as arguments to JUnit test method through the DataProvider feature, they
-   * are printed to surefire report. Some of those chars breaks the parsing of the surefire report during sonar analysis.
-   * Therefor, we only use a subset of the trimmable chars.
-   */
-  private static String randomTrimmedChars(int length, Random random) {
-    char[] chars = new char[length];
-    for (int i = 0; i < chars.length; i++) {
-      chars[i] = SOME_PRINTABLE_TRIMMABLE_CHARS[random.nextInt(SOME_PRINTABLE_TRIMMABLE_CHARS.length)];
-    }
-    return new String(chars);
-  }
-}
index e9c781e7e472733ee6f686136eda7478240e5c5d..08ed5c8e6c8f82c4674518d3990f732f7480a60c 100644 (file)
@@ -14,6 +14,7 @@ dependencies {
   compile 'commons-lang:commons-lang'
   compile 'com.google.code.gson:gson'
   compile 'com.google.guava:guava'
+  compile 'org.apache.commons:commons-csv'
 
   // shaded, but not relocated
   compile project(':sonar-check-api')
index d9d59a0932b51cb3d8a57738a3afe588bfb43783..86acd4cb64bb980fd8132bc8527741387e5ae1f4 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.api.batch.fs.internal;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.util.Collections;
@@ -28,10 +29,17 @@ import java.util.HashMap;
 import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.concurrent.Immutable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
 
 @Immutable
 public abstract class AbstractProjectOrModule extends DefaultInputComponent {
+
+  private static final Logger LOG = Loggers.get(AbstractProjectOrModule.class);
+
   private final Path baseDir;
   private final Path workDir;
   private final String name;
@@ -45,13 +53,7 @@ public abstract class AbstractProjectOrModule extends DefaultInputComponent {
 
   private final String key;
   private final ProjectDefinition definition;
-
-  /**
-   * For testing only!
-   */
-  public AbstractProjectOrModule(ProjectDefinition definition) {
-    this(definition, TestInputFileBuilder.nextBatchId());
-  }
+  private final Charset encoding;
 
   public AbstractProjectOrModule(ProjectDefinition definition, int scannerComponentId) {
     super(scannerComponentId);
@@ -68,6 +70,18 @@ public abstract class AbstractProjectOrModule extends DefaultInputComponent {
 
     this.definition = definition;
     this.key = definition.getKey();
+    this.encoding = initEncoding(definition);
+  }
+
+  private static Charset initEncoding(ProjectDefinition module) {
+    String encodingStr = module.properties().get(CoreProperties.ENCODING_PROPERTY);
+    Charset result;
+    if (StringUtils.isNotEmpty(encodingStr)) {
+      result = Charset.forName(StringUtils.trim(encodingStr));
+    } else {
+      result = Charset.defaultCharset();
+    }
+    return result;
   }
 
   private static Path initBaseDir(ProjectDefinition module) {
@@ -144,5 +158,8 @@ public abstract class AbstractProjectOrModule extends DefaultInputComponent {
   public String getDescription() {
     return description;
   }
-  
+
+  public Charset getEncoding() {
+    return encoding;
+  }
 }
index 157e5a1e25dd1aeb5f7b3870e7da793b8ac929a1..3fe625798c4490ac4e31d7daf133d41c5b36da8b 100644 (file)
  */
 package org.sonar.api.batch.fs.internal;
 
+import java.io.File;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputModule;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import static org.sonar.api.config.internal.MultivalueProperty.parseAsCsv;
 
 @Immutable
 public class DefaultInputModule extends AbstractProjectOrModule implements InputModule {
 
+  private static final Logger LOG = Loggers.get(DefaultInputModule.class);
+  private final List<Path> sourceDirsOrFiles;
+  private final List<Path> testDirsOrFiles;
+
+  /**
+   * For testing only!
+   */
   public DefaultInputModule(ProjectDefinition definition) {
-    super(definition);
+    this(definition, 0);
   }
 
   public DefaultInputModule(ProjectDefinition definition, int scannerComponentId) {
     super(definition, scannerComponentId);
+
+    this.sourceDirsOrFiles = initSources(definition, ProjectDefinition.SOURCES_PROPERTY);
+    this.testDirsOrFiles = initSources(definition, ProjectDefinition.TESTS_PROPERTY);
+  }
+
+  private List<Path> initSources(ProjectDefinition module, String propertyKey) {
+    List<Path> result = new ArrayList<>();
+    PathResolver pathResolver = new PathResolver();
+    String srcPropValue = module.properties().get(propertyKey);
+    if (srcPropValue != null) {
+      for (String sourcePath : parseAsCsv(propertyKey, srcPropValue)) {
+        File dirOrFile = pathResolver.relativeFile(getBaseDir().toFile(), sourcePath);
+        if (dirOrFile.exists()) {
+          result.add(dirOrFile.toPath());
+        }
+      }
+    }
+    return result;
+  }
+
+  public List<Path> getSourceDirsOrFiles() {
+    return sourceDirsOrFiles;
+  }
+
+  public List<Path> getTestDirsOrFiles() {
+    return testDirsOrFiles;
   }
 }
index b89a88465344a0990f04c9ad31ca9e44d0d1bd87..cfdfed95ec0a170ec60ba905a06c7bbe56ef51d4 100644 (file)
@@ -26,8 +26,11 @@ import org.sonar.api.scanner.fs.InputProject;
 @Immutable
 public class DefaultInputProject extends AbstractProjectOrModule implements InputProject {
 
+  /**
+   * For testing only!
+   */
   public DefaultInputProject(ProjectDefinition definition) {
-    super(definition);
+    super(definition, 0);
   }
 
   public DefaultInputProject(ProjectDefinition definition, int scannerComponentId) {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/config/internal/MultivalueProperty.java b/sonar-plugin-api/src/main/java/org/sonar/api/config/internal/MultivalueProperty.java
new file mode 100644 (file)
index 0000000..bf0bb1b
--- /dev/null
@@ -0,0 +1,210 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.config.internal;
+
+import com.google.common.annotations.VisibleForTesting;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.function.Function;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+import org.apache.commons.lang.ArrayUtils;
+
+public class MultivalueProperty {
+  private MultivalueProperty() {
+    // prevents instantiation
+  }
+
+  public static String[] parseAsCsv(String key, String value) {
+    return parseAsCsv(key, value, Function.identity());
+  }
+
+  public static String[] parseAsCsv(String key, String value, Function<String, String> valueProcessor) {
+    String cleanValue = MultivalueProperty.trimFieldsAndRemoveEmptyFields(value);
+    List<String> result = new ArrayList<>();
+    try (CSVParser csvParser = CSVFormat.RFC4180
+      .withHeader((String) null)
+      .withIgnoreEmptyLines()
+      .withIgnoreSurroundingSpaces()
+      .parse(new StringReader(cleanValue))) {
+      List<CSVRecord> records = csvParser.getRecords();
+      if (records.isEmpty()) {
+        return ArrayUtils.EMPTY_STRING_ARRAY;
+      }
+      processRecords(result, records, valueProcessor);
+      return result.toArray(new String[result.size()]);
+    } catch (IOException e) {
+      throw new IllegalStateException("Property: '" + key + "' doesn't contain a valid CSV value: '" + value + "'", e);
+    }
+  }
+
+  /**
+   * In most cases we expect a single record. <br>Having multiple records means the input value was splitted over multiple lines (this is common in Maven).
+   * For example:
+   * <pre>
+   *   &lt;sonar.exclusions&gt;
+   *     src/foo,
+   *     src/bar,
+   *     src/biz
+   *   &lt;sonar.exclusions&gt;
+   * </pre>
+   * In this case records will be merged to form a single list of items. Last item of a record is appended to first item of next record.
+   * <p>
+   * This is a very curious case, but we try to preserve line break in the middle of an item:
+   * <pre>
+   *   &lt;sonar.exclusions&gt;
+   *     a
+   *     b,
+   *     c
+   *   &lt;sonar.exclusions&gt;
+   * </pre>
+   * will produce ['a\nb', 'c']
+   */
+  private static void processRecords(List<String> result, List<CSVRecord> records, Function<String, String> valueProcessor) {
+    for (CSVRecord csvRecord : records) {
+      Iterator<String> it = csvRecord.iterator();
+      if (!result.isEmpty()) {
+        String next = it.next();
+        if (!next.isEmpty()) {
+          int lastItemIdx = result.size() - 1;
+          String previous = result.get(lastItemIdx);
+          if (previous.isEmpty()) {
+            result.set(lastItemIdx, valueProcessor.apply(next));
+          } else {
+            result.set(lastItemIdx, valueProcessor.apply(previous + "\n" + next));
+          }
+        }
+      }
+      it.forEachRemaining(s -> {
+        String apply = valueProcessor.apply(s);
+        result.add(apply);
+      });
+    }
+  }
+
+  /**
+   * Removes the empty fields from the value of a multi-value property from empty fields, including trimming each field.
+   * <p>
+   * Quotes can be used to prevent an empty field to be removed (as it is used to preserve empty spaces).
+   * <ul>
+   *    <li>{@code "" => ""}</li>
+   *    <li>{@code " " => ""}</li>
+   *    <li>{@code "," => ""}</li>
+   *    <li>{@code ",," => ""}</li>
+   *    <li>{@code ",,," => ""}</li>
+   *    <li>{@code ",a" => "a"}</li>
+   *    <li>{@code "a," => "a"}</li>
+   *    <li>{@code ",a," => "a"}</li>
+   *    <li>{@code "a,,b" => "a,b"}</li>
+   *    <li>{@code "a,   ,b" => "a,b"}</li>
+   *    <li>{@code "a,\"\",b" => "a,b"}</li>
+   *    <li>{@code "\"a\",\"b\"" => "\"a\",\"b\""}</li>
+   *    <li>{@code "\"  a  \",\"b \"" => "\"  a  \",\"b \""}</li>
+   *    <li>{@code "\"a\",\"\",\"b\"" => "\"a\",\"\",\"b\""}</li>
+   *    <li>{@code "\"a\",\"  \",\"b\"" => "\"a\",\"  \",\"b\""}</li>
+   *    <li>{@code "\"  a,,b,c  \",\"d \"" => "\"  a,,b,c  \",\"d \""}</li>
+   *    <li>{@code "a,\"  \",b" => "ab"]}</li>
+   * </ul>
+   */
+  @VisibleForTesting
+  static String trimFieldsAndRemoveEmptyFields(String str) {
+    char[] chars = str.toCharArray();
+    char[] res = new char[chars.length];
+    /*
+     * set when reading the first non trimmable char after a separator char (or the beginning of the string)
+     * unset when reading a separator
+     */
+    boolean inField = false;
+    boolean inQuotes = false;
+    int i = 0;
+    int resI = 0;
+    for (; i < chars.length; i++) {
+      boolean isSeparator = chars[i] == ',';
+      if (!inQuotes && isSeparator) {
+        // exiting field (may already be unset)
+        inField = false;
+        if (resI > 0) {
+          resI = retroTrim(res, resI);
+        }
+      } else {
+        boolean isTrimmed = !inQuotes && istrimmable(chars[i]);
+        if (isTrimmed && !inField) {
+          // we haven't meet any non trimmable char since the last separator yet
+          continue;
+        }
+
+        boolean isEscape = isEscapeChar(chars[i]);
+        if (isEscape) {
+          inQuotes = !inQuotes;
+        }
+
+        // add separator as we already had one field
+        if (!inField && resI > 0) {
+          res[resI] = ',';
+          resI++;
+        }
+
+        // register in field (may already be set)
+        inField = true;
+        // copy current char
+        res[resI] = chars[i];
+        resI++;
+      }
+    }
+    // inQuotes can only be true at this point if quotes are unbalanced
+    if (!inQuotes) {
+      // trim end of str
+      resI = retroTrim(res, resI);
+    }
+    return new String(res, 0, resI);
+  }
+
+  private static boolean isEscapeChar(char aChar) {
+    return aChar == '"';
+  }
+
+  private static boolean istrimmable(char aChar) {
+    return aChar <= ' ';
+  }
+
+  /**
+   * Reads from index {@code resI} to the beginning into {@code res} looking up the location of the trimmable char with
+   * the lowest index before encountering a non-trimmable char.
+   * <p>
+   * This basically trims {@code res} from any trimmable char at its end.
+   *
+   * @return index of next location to put new char in res
+   */
+  private static int retroTrim(char[] res, int resI) {
+    int i = resI;
+    while (i >= 1) {
+      if (!istrimmable(res[i - 1])) {
+        return i;
+      }
+      i--;
+    }
+    return i;
+  }
+
+}
index 43bd60d5bd61a64bd52b7601132858c1e39971e5..e98b77161d41cd5f741d4372591834fa6b2bc8e6 100644 (file)
@@ -32,7 +32,9 @@ import org.sonar.api.config.Configuration;
  * runtime.
  *
  * @since 3.5
+ * @deprecated since 7.6
  */
+@Deprecated
 @ScannerSide
 public class FileExclusions {
   private final Configuration settings;
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/config/internal/MultivaluePropertyTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/config/internal/MultivaluePropertyTest.java
new file mode 100644 (file)
index 0000000..bd50d94
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2019 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.config.internal;
+
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Random;
+import java.util.function.Function;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.api.config.internal.MultivalueProperty.parseAsCsv;
+import static org.sonar.api.config.internal.MultivalueProperty.trimFieldsAndRemoveEmptyFields;
+
+@RunWith(DataProviderRunner.class)
+public class MultivaluePropertyTest {
+  private static final String[] EMPTY_STRING_ARRAY = {};
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  @UseDataProvider("testParseAsCsv")
+  public void parseAsCsv_for_coverage(String value, String[] expected) {
+    // parseAsCsv is extensively tested in org.sonar.server.config.ConfigurationProviderTest
+    assertThat(parseAsCsv("key", value))
+      .isEqualTo(parseAsCsv("key", value, Function.identity()))
+      .isEqualTo(expected);
+  }
+
+  @Test
+  public void parseAsCsv_fails_with_ISE_if_value_can_not_be_parsed() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Property: 'multi' doesn't contain a valid CSV value: '\"a ,b'");
+
+    parseAsCsv("multi", "\"a ,b");
+  }
+
+  @DataProvider
+  public static Object[][] testParseAsCsv() {
+    return new Object[][] {
+      {"a", arrayOf("a")},
+      {" a", arrayOf("a")},
+      {"a ", arrayOf("a")},
+      {" a, b", arrayOf("a", "b")},
+      {"a,b ", arrayOf("a", "b")},
+      {"a,,,b,c,,d", arrayOf("a", "b", "c", "d")},
+      {" , \n ,, \t", EMPTY_STRING_ARRAY},
+      {"\" a\"", arrayOf(" a")},
+      {"\",\"", arrayOf(",")},
+      // escaped quote in quoted field
+      {"\"\"\"\"", arrayOf("\"")}
+    };
+  }
+
+  private static String[] arrayOf(String... strs) {
+    return strs;
+  }
+
+  @Test
+  public void trimFieldsAndRemoveEmptyFields_throws_NPE_if_arg_is_null() {
+    expectedException.expect(NullPointerException.class);
+
+    trimFieldsAndRemoveEmptyFields(null);
+  }
+
+  @Test
+  @UseDataProvider("plains")
+  public void trimFieldsAndRemoveEmptyFields_ignores_EmptyFields(String str) {
+    assertThat(trimFieldsAndRemoveEmptyFields("")).isEqualTo("");
+    assertThat(trimFieldsAndRemoveEmptyFields(str)).isEqualTo(str);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + str)).isEqualTo(str);
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ',')).isEqualTo(str);
+    assertThat(trimFieldsAndRemoveEmptyFields(",,," + str)).isEqualTo(str);
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,,")).isEqualTo(str);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str)).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,," + str)).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str)).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields("," + str + ",,," + str)).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(",,," + str + ",,," + str)).isEqualTo(str + ',' + str);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',')).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,," + str + ",")).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ",,," + str + ",,")).isEqualTo(str + ',' + str);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str + ',')).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(",," + str + ',' + str + ',')).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ",," + str + ',')).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + str + ',' + str + ",,")).isEqualTo(str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(",,," + str + ",,," + str + ",,")).isEqualTo(str + ',' + str);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',' + str)).isEqualTo(str + ',' + str + ',' + str);
+    assertThat(trimFieldsAndRemoveEmptyFields(str + ',' + str + ',' + str)).isEqualTo(str + ',' + str + ',' + str);
+  }
+
+  @DataProvider
+  public static Object[][] plains() {
+    return new Object[][] {
+      {randomAlphanumeric(1)},
+      {randomAlphanumeric(2)},
+      {randomAlphanumeric(3 + new Random().nextInt(5))}
+    };
+  }
+
+  @Test
+  @UseDataProvider("emptyAndtrimmable")
+  public void trimFieldsAndRemoveEmptyFields_ignores_empty_fields_and_trims_fields(String empty, String trimmable) {
+    String expected = trimmable.trim();
+    assertThat(empty.trim()).isEmpty();
+
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + empty)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ",," + empty)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + trimmable)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(empty + ",," + trimmable)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + trimmable + ',' + empty)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(empty + ",," + trimmable + ",,," + empty)).isEqualTo(expected);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + empty + ',' + empty)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ",," + empty + ",,," + empty)).isEqualTo(expected);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(empty + ',' + empty + ',' + trimmable)).isEqualTo(expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(empty + ",,,," + empty + ",," + trimmable)).isEqualTo(expected);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + ',' + trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected + ',' + expected);
+    assertThat(trimFieldsAndRemoveEmptyFields(trimmable + "," + trimmable + ',' + trimmable)).isEqualTo(expected + ',' + expected + ',' + expected);
+  }
+
+  @Test
+  public void trimAccordingToStringTrim() {
+    String str = randomAlphanumeric(4);
+    for (int i = 0; i <= ' '; i++) {
+      String prefixed = (char) i + str;
+      String suffixed = (char) i + str;
+      String both = (char) i + str + (char) i;
+      assertThat(trimFieldsAndRemoveEmptyFields(prefixed)).isEqualTo(prefixed.trim());
+      assertThat(trimFieldsAndRemoveEmptyFields(suffixed)).isEqualTo(suffixed.trim());
+      assertThat(trimFieldsAndRemoveEmptyFields(both)).isEqualTo(both.trim());
+    }
+  }
+
+  @DataProvider
+  public static Object[][] emptyAndtrimmable() {
+    Random random = new Random();
+    String oneEmpty = randomTrimmedChars(1, random);
+    String twoEmpty = randomTrimmedChars(2, random);
+    String threePlusEmpty = randomTrimmedChars(3 + random.nextInt(5), random);
+    String onePlusEmpty = randomTrimmedChars(1 + random.nextInt(5), random);
+
+    String plain = randomAlphanumeric(1);
+    String plainWithtrimmable = randomAlphanumeric(2) + onePlusEmpty + randomAlphanumeric(3);
+    String quotedWithSeparator = '"' + randomAlphanumeric(3) + ',' + randomAlphanumeric(2) + '"';
+    String quotedWithDoubleSeparator = '"' + randomAlphanumeric(3) + ",," + randomAlphanumeric(2) + '"';
+    String quotedWithtrimmable = '"' + randomAlphanumeric(3) + onePlusEmpty + randomAlphanumeric(2) + '"';
+
+    String[] empties = {oneEmpty, twoEmpty, threePlusEmpty};
+    String[] strings = {plain, plainWithtrimmable,
+      onePlusEmpty + plain, plain + onePlusEmpty, onePlusEmpty + plain + onePlusEmpty,
+      onePlusEmpty + plainWithtrimmable, plainWithtrimmable + onePlusEmpty, onePlusEmpty + plainWithtrimmable + onePlusEmpty,
+      onePlusEmpty + quotedWithSeparator, quotedWithSeparator + onePlusEmpty, onePlusEmpty + quotedWithSeparator + onePlusEmpty,
+      onePlusEmpty + quotedWithDoubleSeparator, quotedWithDoubleSeparator + onePlusEmpty, onePlusEmpty + quotedWithDoubleSeparator + onePlusEmpty,
+      onePlusEmpty + quotedWithtrimmable, quotedWithtrimmable + onePlusEmpty, onePlusEmpty + quotedWithtrimmable + onePlusEmpty
+    };
+
+    Object[][] res = new Object[empties.length * strings.length][2];
+    int i = 0;
+    for (String empty : empties) {
+      for (String string : strings) {
+        res[i][0] = empty;
+        res[i][1] = string;
+        i++;
+      }
+    }
+    return res;
+  }
+
+  @Test
+  @UseDataProvider("emptys")
+  public void trimFieldsAndRemoveEmptyFields_quotes_allow_to_preserve_fields(String empty) {
+    String quotedEmpty = '"' + empty + '"';
+
+    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty)).isEqualTo(quotedEmpty);
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + quotedEmpty)).isEqualTo(quotedEmpty);
+    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',')).isEqualTo(quotedEmpty);
+    assertThat(trimFieldsAndRemoveEmptyFields(',' + quotedEmpty + ',')).isEqualTo(quotedEmpty);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',' + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty);
+    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ",," + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty);
+
+    assertThat(trimFieldsAndRemoveEmptyFields(quotedEmpty + ',' + quotedEmpty + ',' + quotedEmpty)).isEqualTo(quotedEmpty + ',' + quotedEmpty + ',' + quotedEmpty);
+  }
+
+  @DataProvider
+  public static Object[][] emptys() {
+    Random random = new Random();
+    return new Object[][] {
+      {randomTrimmedChars(1, random)},
+      {randomTrimmedChars(2, random)},
+      {randomTrimmedChars(3 + random.nextInt(5), random)}
+    };
+  }
+
+  @Test
+  public void trimFieldsAndRemoveEmptyFields_supports_escaped_quote_in_quotes() {
+    assertThat(trimFieldsAndRemoveEmptyFields("\"f\"\"oo\"")).isEqualTo("\"f\"\"oo\"");
+    assertThat(trimFieldsAndRemoveEmptyFields("\"f\"\"oo\",\"bar\"\"\"")).isEqualTo("\"f\"\"oo\",\"bar\"\"\"");
+  }
+
+  @Test
+  public void trimFieldsAndRemoveEmptyFields_does_not_fail_on_unbalanced_quotes() {
+    assertThat(trimFieldsAndRemoveEmptyFields("\"")).isEqualTo("\"");
+    assertThat(trimFieldsAndRemoveEmptyFields("\"foo")).isEqualTo("\"foo");
+    assertThat(trimFieldsAndRemoveEmptyFields("foo\"")).isEqualTo("foo\"");
+
+    assertThat(trimFieldsAndRemoveEmptyFields("\"foo\",\"")).isEqualTo("\"foo\",\"");
+    assertThat(trimFieldsAndRemoveEmptyFields("\",\"foo\"")).isEqualTo("\",\"foo\"");
+
+    assertThat(trimFieldsAndRemoveEmptyFields("\"foo\",\",  ")).isEqualTo("\"foo\",\",  ");
+
+    assertThat(trimFieldsAndRemoveEmptyFields(" a ,,b , c,  \"foo\",\"  ")).isEqualTo("a,b,c,\"foo\",\"  ");
+    assertThat(trimFieldsAndRemoveEmptyFields("\" a ,,b , c,  ")).isEqualTo("\" a ,,b , c,  ");
+  }
+
+  private static final char[] SOME_PRINTABLE_TRIMMABLE_CHARS = {
+    ' ', '\t', '\n', '\r'
+  };
+
+  /**
+   * Result of randomTrimmedChars being used as arguments to JUnit test method through the DataProvider feature, they
+   * are printed to surefire report. Some of those chars breaks the parsing of the surefire report during sonar analysis.
+   * Therefor, we only use a subset of the trimmable chars.
+   */
+  private static String randomTrimmedChars(int length, Random random) {
+    char[] chars = new char[length];
+    for (int i = 0; i < chars.length; i++) {
+      chars[i] = SOME_PRINTABLE_TRIMMABLE_CHARS[random.nextInt(SOME_PRINTABLE_TRIMMABLE_CHARS.length)];
+    }
+    return new String(chars);
+  }
+}
index 5567fed1c98b417da171753342cf686a51fd77c9..f79c496b96292d8f7867edef7a7663249d7db91e 100644 (file)
@@ -36,7 +36,7 @@ import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
 
 import static java.util.Objects.requireNonNull;
 import static org.apache.commons.lang.StringUtils.trim;
-import static org.sonar.core.config.MultivalueProperty.parseAsCsv;
+import static org.sonar.api.config.internal.MultivalueProperty.parseAsCsv;
 
 @Immutable
 public abstract class DefaultConfiguration implements Configuration {
index 6392a5dd51584453d21815696bcc48c0e9f51c9c..3026296253b1dbc32d4af1b735303a59d73ae1cc 100644 (file)
  */
 package org.sonar.scanner.scan;
 
+import java.nio.file.Path;
 import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
+import org.apache.commons.lang.StringUtils;
 import org.picocontainer.injectors.ProviderAdapter;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.DefaultInputProject;
+import org.sonar.api.scan.filesystem.PathResolver;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
 import org.sonar.scanner.scan.filesystem.ScannerComponentIdGenerator;
 
 public class InputModuleHierarchyProvider extends ProviderAdapter {
 
+  private static final Logger LOG = Loggers.get(InputModuleHierarchyProvider.class);
+
   private DefaultInputModuleHierarchy hierarchy = null;
 
   public DefaultInputModuleHierarchy provide(ScannerComponentIdGenerator scannerComponentIdGenerator, DefaultInputProject project) {
     if (hierarchy == null) {
-      DefaultInputModule root = new DefaultInputModule(project.definition(), project.scannerId());
+      LOG.debug("Creating module hierarchy");
+      DefaultInputModule root = createModule(project.definition(), project.scannerId());
       Map<DefaultInputModule, DefaultInputModule> parents = createChildren(root, scannerComponentIdGenerator, new HashMap<>());
       if (parents.isEmpty()) {
         hierarchy = new DefaultInputModuleHierarchy(root);
@@ -47,10 +59,44 @@ public class InputModuleHierarchyProvider extends ProviderAdapter {
   private static Map<DefaultInputModule, DefaultInputModule> createChildren(DefaultInputModule parent, ScannerComponentIdGenerator scannerComponentIdGenerator,
                                                                                       Map<DefaultInputModule, DefaultInputModule> parents) {
     for (ProjectDefinition def : parent.definition().getSubProjects()) {
-      DefaultInputModule child = new DefaultInputModule(def, scannerComponentIdGenerator.getAsInt());
+      DefaultInputModule child = createModule(def, scannerComponentIdGenerator.getAsInt());
       parents.put(child, parent);
       createChildren(child, scannerComponentIdGenerator, parents);
     }
     return parents;
   }
+
+  private static DefaultInputModule createModule(ProjectDefinition def, int scannerComponentId) {
+    LOG.debug("  Init module '{}'", def.getName());
+    DefaultInputModule module = new DefaultInputModule(def, scannerComponentId);
+    LOG.debug("    Base dir: {}", module.getBaseDir().toAbsolutePath().toString());
+    LOG.debug("    Working dir: {}", module.getWorkDir().toAbsolutePath().toString());
+    LOG.debug("    Module global encoding: {}, default locale: {}", module.getEncoding().displayName(), Locale.getDefault());
+    logPaths("    Source paths: ", module.getBaseDir(), module.getSourceDirsOrFiles());
+    logPaths("    Test paths: ", module.getBaseDir(), module.getTestDirsOrFiles());
+    return module;
+  }
+
+  private static void logPaths(String label, Path baseDir, List<Path> paths) {
+    if (!paths.isEmpty()) {
+      StringBuilder sb = new StringBuilder(label);
+      for (Iterator<Path> it = paths.iterator(); it.hasNext(); ) {
+        Path file = it.next();
+        Optional<String> relativePathToBaseDir = PathResolver.relativize(baseDir, file);
+        if (!relativePathToBaseDir.isPresent()) {
+          sb.append(file);
+        } else if (StringUtils.isBlank(relativePathToBaseDir.get())) {
+          sb.append(".");
+        } else {
+          sb.append(relativePathToBaseDir.get());
+        }
+        if (it.hasNext()) {
+          sb.append(", ");
+        }
+      }
+      LOG.info(sb.toString());
+    }
+  }
+
+
 }
index 4363fb6b19ed89123ac26149239426061862826a..ea05d64e4b15d95ca509ff87a1f7fc3e2b230f67 100644 (file)
  */
 package org.sonar.scanner.scan;
 
+import java.util.Locale;
 import org.picocontainer.injectors.ProviderAdapter;
 import org.sonar.api.batch.bootstrap.ProjectReactor;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.DefaultInputProject;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
 import org.sonar.scanner.scan.filesystem.ScannerComponentIdGenerator;
 
 public class InputProjectProvider extends ProviderAdapter {
 
+  private static final Logger LOG = Loggers.get(DefaultInputModule.class);
+
   private DefaultInputProject project = null;
 
   public DefaultInputProject provide(ProjectBuildersExecutor projectBuildersExecutor, ProjectReactorValidator validator,
@@ -37,8 +43,12 @@ public class InputProjectProvider extends ProviderAdapter {
       // 2 Validate final reactor
       validator.validate(projectReactor);
 
-      // 3 Create modules and the hierarchy
+      // 3 Create project
       project = new DefaultInputProject(projectReactor.getRoot(), scannerComponentIdGenerator.getAsInt());
+
+      LOG.info("Base dir: {}", project.getBaseDir().toAbsolutePath().toString());
+      LOG.info("Working dir: {}", project.getWorkDir().toAbsolutePath().toString());
+      LOG.debug("Project global encoding: {}, default locale: {}", project.getEncoding().displayName(), Locale.getDefault());
     }
     return project;
   }
index 5a45671270ae52377321254485dce1967f922956..bf101a42de04615443d837d9c5ae951571c9efe4 100644 (file)
@@ -52,9 +52,7 @@ import org.sonar.scanner.scan.filesystem.DefaultModuleFileSystem;
 import org.sonar.scanner.scan.filesystem.ExclusionFilters;
 import org.sonar.scanner.scan.filesystem.FileIndexer;
 import org.sonar.scanner.scan.filesystem.InputFileBuilder;
-import org.sonar.scanner.scan.filesystem.LanguageDetection;
 import org.sonar.scanner.scan.filesystem.MetadataGenerator;
-import org.sonar.scanner.scan.filesystem.ModuleFileSystemInitializer;
 import org.sonar.scanner.scan.filesystem.ModuleInputComponentStore;
 import org.sonar.scanner.scan.report.IssuesReports;
 import org.sonar.scanner.sensor.DefaultSensorContext;
@@ -110,11 +108,9 @@ public class ModuleScanContainer extends ComponentContainer {
       ExclusionFilters.class,
       MetadataGenerator.class,
       FileMetadata.class,
-      LanguageDetection.class,
       FileIndexer.class,
       InputFileBuilder.class,
       DefaultModuleFileSystem.class,
-      ModuleFileSystemInitializer.class,
       QProfileVerifier.class,
 
       SensorOptimizer.class,
index f9667af57b1acd8176ab359d13ecb56b0463d0b9..38d0d60733fe0d7bf10ef51f4d2555c5da53e44f 100644 (file)
@@ -45,7 +45,8 @@ import org.sonar.api.utils.log.Profiler;
 import org.sonar.scanner.bootstrap.ScannerProperties;
 import org.sonar.scanner.util.ScannerUtils;
 
-import static org.sonar.core.config.MultivalueProperty.parseAsCsv;
+import static org.sonar.api.config.internal.MultivalueProperty.parseAsCsv;
+
 
 /**
  * Class that creates a project definition based on a set of properties.
index 332bb65299a4cb165b32d26bb42377ac9185b584..eb33fe12f42d402c0b5c485262ac66da3cf11d2d 100644 (file)
@@ -93,6 +93,7 @@ import org.sonar.scanner.scan.branch.BranchType;
 import org.sonar.scanner.scan.branch.ProjectBranchesProvider;
 import org.sonar.scanner.scan.branch.ProjectPullRequestsProvider;
 import org.sonar.scanner.scan.filesystem.InputComponentStore;
+import org.sonar.scanner.scan.filesystem.LanguageDetection;
 import org.sonar.scanner.scan.filesystem.ScannerComponentIdGenerator;
 import org.sonar.scanner.scan.filesystem.StatusDetection;
 import org.sonar.scanner.scan.measure.DefaultMetricFinder;
@@ -163,6 +164,7 @@ public class ProjectScanContainer extends ComponentContainer {
       ScannerComponentIdGenerator.class,
       new ScmChangedFilesProvider(),
       StatusDetection.class,
+      LanguageDetection.class,
 
       // rules
       new ActiveRulesProvider(),
index 4e9dc9406de25164e20f788885b44964cebafc3a..3a962530f8bab32e3725f64788b68ad421117914 100644 (file)
@@ -21,26 +21,26 @@ package org.sonar.scanner.scan.filesystem;
 
 import com.google.common.annotations.VisibleForTesting;
 import org.sonar.api.batch.fs.internal.DefaultFileSystem;
-import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.scanner.analysis.DefaultAnalysisMode;
 
 public class DefaultModuleFileSystem extends DefaultFileSystem {
 
-  public DefaultModuleFileSystem(ModuleInputComponentStore moduleInputFileCache, AbstractProjectOrModule module, ModuleFileSystemInitializer initializer, DefaultAnalysisMode mode,
+  public DefaultModuleFileSystem(ModuleInputComponentStore moduleInputFileCache, DefaultInputModule module, DefaultAnalysisMode mode,
                                  StatusDetection statusDetection) {
     super(module.getBaseDir(), moduleInputFileCache);
-    setFields(module, initializer, mode, statusDetection);
+    setFields(module, mode, statusDetection);
   }
 
   @VisibleForTesting
-  public DefaultModuleFileSystem(AbstractProjectOrModule module, ModuleFileSystemInitializer initializer, DefaultAnalysisMode mode, StatusDetection statusDetection) {
+  public DefaultModuleFileSystem(DefaultInputModule module, DefaultAnalysisMode mode, StatusDetection statusDetection) {
     super(module.getBaseDir());
-    setFields(module, initializer, mode, statusDetection);
+    setFields(module, mode, statusDetection);
   }
 
-  private void setFields(AbstractProjectOrModule module, ModuleFileSystemInitializer initializer, DefaultAnalysisMode mode, StatusDetection statusDetection) {
+  private void setFields(DefaultInputModule module, DefaultAnalysisMode mode, StatusDetection statusDetection) {
     setWorkDir(module.getWorkDir());
-    setEncoding(initializer.defaultEncoding());
+    setEncoding(module.getEncoding());
 
     // filter the files sensors have access to
     if (!mode.scanAllFiles()) {
index 7779852b181bf775ac425ad0e19af4639f7c4289..5294797c67cbce52c0c634e403a7b79ef47b011f 100644 (file)
@@ -44,10 +44,9 @@ import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.fs.InputFile;
 import org.sonar.api.batch.fs.InputFile.Type;
 import org.sonar.api.batch.fs.InputFileFilter;
-import org.sonar.api.batch.fs.InputModule;
 import org.sonar.api.batch.fs.internal.DefaultInputDir;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.scan.filesystem.PathResolver;
 import org.sonar.api.utils.MessageException;
 import org.sonar.scanner.scan.DefaultComponentTree;
@@ -64,10 +63,9 @@ public class FileIndexer {
   private final ExclusionFilters exclusionFilters;
   private final InputFileBuilder inputFileBuilder;
   private final DefaultComponentTree componentTree;
-  private final AbstractProjectOrModule module;
+  private final DefaultInputModule module;
   private final ScannerComponentIdGenerator scannerComponentIdGenerator;
   private final InputComponentStore componentStore;
-  private final ModuleFileSystemInitializer moduleFileSystemInitializer;
   private ExecutorService executorService;
   private final List<Future<Void>> tasks;
   private final DefaultModuleFileSystem defaultModuleFileSystem;
@@ -75,16 +73,15 @@ public class FileIndexer {
 
   private ProgressReport progressReport;
 
-  public FileIndexer(ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, InputModule module, ExclusionFilters exclusionFilters,
-                     DefaultComponentTree componentTree, InputFileBuilder inputFileBuilder, ModuleFileSystemInitializer initializer, DefaultModuleFileSystem defaultModuleFileSystem,
+  public FileIndexer(ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, DefaultInputModule module, ExclusionFilters exclusionFilters,
+                     DefaultComponentTree componentTree, InputFileBuilder inputFileBuilder, DefaultModuleFileSystem defaultModuleFileSystem,
                      LanguageDetection languageDetection,
                      InputFileFilter[] filters) {
     this.scannerComponentIdGenerator = scannerComponentIdGenerator;
     this.componentStore = componentStore;
-    this.module = (AbstractProjectOrModule) module;
+    this.module = module;
     this.componentTree = componentTree;
     this.inputFileBuilder = inputFileBuilder;
-    this.moduleFileSystemInitializer = initializer;
     this.defaultModuleFileSystem = defaultModuleFileSystem;
     this.langDetection = languageDetection;
     this.filters = filters;
@@ -92,10 +89,10 @@ public class FileIndexer {
     this.tasks = new ArrayList<>();
   }
 
-  public FileIndexer(ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, InputModule module, ExclusionFilters exclusionFilters,
-                     DefaultComponentTree componentTree, InputFileBuilder inputFileBuilder, ModuleFileSystemInitializer initializer, DefaultModuleFileSystem defaultModuleFileSystem,
+  public FileIndexer(ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, DefaultInputModule module, ExclusionFilters exclusionFilters,
+                     DefaultComponentTree componentTree, InputFileBuilder inputFileBuilder, DefaultModuleFileSystem defaultModuleFileSystem,
                      LanguageDetection languageDetection) {
-    this(scannerComponentIdGenerator, componentStore, module, exclusionFilters, componentTree, inputFileBuilder, initializer, defaultModuleFileSystem, languageDetection,
+    this(scannerComponentIdGenerator, componentStore, module, exclusionFilters, componentTree, inputFileBuilder, defaultModuleFileSystem, languageDetection,
       new InputFileFilter[0]);
   }
 
@@ -112,8 +109,8 @@ public class FileIndexer {
 
     Progress progress = new Progress();
 
-    indexFiles(moduleFileSystemInitializer.sources(), InputFile.Type.MAIN, progress);
-    indexFiles(moduleFileSystemInitializer.tests(), InputFile.Type.TEST, progress);
+    indexFiles(module.getSourceDirsOrFiles(), InputFile.Type.MAIN, progress);
+    indexFiles(module.getTestDirsOrFiles(), InputFile.Type.TEST, progress);
 
     waitForTasksToComplete(progressReport);
 
index 8d14471b65c4946985802fb400339dda888e3e18..4de7d1bf07e837a32cc5c107a0830e40d5657f76 100644 (file)
@@ -22,43 +22,38 @@ package org.sonar.scanner.scan.filesystem;
 import java.nio.file.Path;
 import javax.annotation.Nullable;
 import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.InputModule;
 import org.sonar.api.batch.fs.internal.DefaultIndexedFile;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
-import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
-import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.DefaultInputProject;
 import org.sonar.api.batch.fs.internal.SensorStrategy;
 import org.sonar.scanner.scan.ScanProperties;
 
 public class InputFileBuilder {
-  private final String moduleKey;
-  private final Path moduleBaseDir;
+  private final DefaultInputModule module;
   private final ScannerComponentIdGenerator idGenerator;
   private final MetadataGenerator metadataGenerator;
   private final boolean preloadMetadata;
-  private final ModuleFileSystemInitializer moduleFileSystemInitializer;
   private final Path projectBaseDir;
   private final SensorStrategy sensorStrategy;
 
-  public InputFileBuilder(InputModule module, MetadataGenerator metadataGenerator,
-                          ScannerComponentIdGenerator idGenerator, ScanProperties properties, ModuleFileSystemInitializer moduleFileSystemInitializer, InputModuleHierarchy hierarchy,
+  public InputFileBuilder(DefaultInputProject project, DefaultInputModule module, MetadataGenerator metadataGenerator,
+                          ScannerComponentIdGenerator idGenerator, ScanProperties properties,
                           SensorStrategy sensorStrategy) {
     this.sensorStrategy = sensorStrategy;
-    this.projectBaseDir = hierarchy.root().getBaseDir();
-    this.moduleFileSystemInitializer = moduleFileSystemInitializer;
-    this.moduleKey = module.key();
-    this.moduleBaseDir = ((AbstractProjectOrModule) module).getBaseDir();
+    this.projectBaseDir = project.getBaseDir();
+    this.module = module;
     this.metadataGenerator = metadataGenerator;
     this.idGenerator = idGenerator;
     this.preloadMetadata = properties.preloadFileMetadata();
   }
 
   DefaultInputFile create(InputFile.Type type, Path absolutePath, @Nullable String language) {
-    DefaultIndexedFile indexedFile = new DefaultIndexedFile(absolutePath, moduleKey,
+    DefaultIndexedFile indexedFile = new DefaultIndexedFile(absolutePath, module.key(),
       projectBaseDir.relativize(absolutePath).toString(),
-      moduleBaseDir.relativize(absolutePath).toString(),
+      module.getBaseDir().relativize(absolutePath).toString(),
       type, language, idGenerator.getAsInt(), sensorStrategy);
-    DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(f, moduleFileSystemInitializer.defaultEncoding()));
+    DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(f, module.getEncoding()));
     if (language != null) {
       inputFile.setPublished(true);
     }
index 0b3dfc68e88783e34805313e3ec44f19484472f9..1b74abd4aa926ba0295c0777e648bd11944b164f 100644 (file)
@@ -33,7 +33,6 @@ import org.apache.commons.lang.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.ScannerSide;
 import org.sonar.api.batch.fs.internal.PathPattern;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.MessageException;
@@ -43,7 +42,6 @@ import org.sonar.scanner.repository.language.LanguagesRepository;
 /**
  * Detect language of a source file based on its suffix and configured patterns.
  */
-@ScannerSide
 @ThreadSafe
 public class LanguageDetection {
 
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializer.java
deleted file mode 100644 (file)
index 8c32cd6..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.scanner.scan.filesystem;
-
-import java.io.File;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.Optional;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.ScannerSide;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.fs.InputModule;
-import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
-import org.sonar.api.scan.filesystem.PathResolver;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-
-import static org.sonar.core.config.MultivalueProperty.parseAsCsv;
-
-@ScannerSide
-@Immutable
-public class ModuleFileSystemInitializer {
-
-  private static final Logger LOG = Loggers.get(ModuleFileSystemInitializer.class);
-
-  private final List<Path> sourceDirsOrFiles;
-  private final List<Path> testDirsOrFiles;
-  private final Charset encoding;
-
-  public ModuleFileSystemInitializer(InputModule inputModule) {
-    AbstractProjectOrModule inputModuleCasted = (AbstractProjectOrModule) inputModule;
-    logDir("Base dir: ", inputModuleCasted.getBaseDir());
-    logDir("Working dir: ", inputModuleCasted.getWorkDir());
-    sourceDirsOrFiles = initSources(inputModuleCasted, ProjectDefinition.SOURCES_PROPERTY, "Source paths: ");
-    testDirsOrFiles = initSources(inputModuleCasted, ProjectDefinition.TESTS_PROPERTY, "Test paths: ");
-    encoding = initEncoding(inputModuleCasted);
-  }
-
-  private static List<Path> initSources(AbstractProjectOrModule module, String propertyKey, String logLabel) {
-    List<Path> result = new ArrayList<>();
-    PathResolver pathResolver = new PathResolver();
-    String srcPropValue = module.properties().get(propertyKey);
-    if (srcPropValue != null) {
-      for (String sourcePath : parseAsCsv(propertyKey, srcPropValue)) {
-        File dirOrFile = pathResolver.relativeFile(module.getBaseDir().toFile(), sourcePath);
-        if (dirOrFile.exists()) {
-          result.add(dirOrFile.toPath());
-        }
-      }
-    }
-    logPaths(logLabel, module.getBaseDir(), result);
-    return result;
-  }
-
-  private static Charset initEncoding(AbstractProjectOrModule module) {
-    String encodingStr = module.properties().get(CoreProperties.ENCODING_PROPERTY);
-    Charset result;
-    if (StringUtils.isNotEmpty(encodingStr)) {
-      result = Charset.forName(StringUtils.trim(encodingStr));
-      LOG.info("Source encoding: {}, default locale: {}", result.displayName(), Locale.getDefault());
-    } else {
-      result = Charset.defaultCharset();
-      LOG.warn("Source encoding is platform dependent ({}), default locale: {}", result.displayName(), Locale.getDefault());
-    }
-    return result;
-  }
-
-  List<Path> sources() {
-    return sourceDirsOrFiles;
-  }
-
-  List<Path> tests() {
-    return testDirsOrFiles;
-  }
-
-  public Charset defaultEncoding() {
-    return encoding;
-  }
-
-  private static void logPaths(String label, Path baseDir, List<Path> paths) {
-    if (!paths.isEmpty()) {
-      StringBuilder sb = new StringBuilder(label);
-      for (Iterator<Path> it = paths.iterator(); it.hasNext(); ) {
-        Path file = it.next();
-        Optional<String> relativePathToBaseDir = PathResolver.relativize(baseDir, file);
-        if (!relativePathToBaseDir.isPresent()) {
-          sb.append(file);
-        } else if (StringUtils.isBlank(relativePathToBaseDir.get())) {
-          sb.append(".");
-        } else {
-          sb.append(relativePathToBaseDir.get());
-        }
-        if (it.hasNext()) {
-          sb.append(", ");
-        }
-      }
-      LOG.info(sb.toString());
-    }
-  }
-
-  private static void logDir(String label, @Nullable Path dir) {
-    if (dir != null) {
-      LOG.info(label + dir.toAbsolutePath().toString());
-    }
-  }
-
-}
index 851eabb2c49431fba100e952246a858aa64a437a..32ba8fbc390c34951c9d0057a5013aefd43192d8 100644 (file)
@@ -27,17 +27,17 @@ import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.CoreProperties;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.InputFile.Type;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.api.batch.fs.internal.DefaultInputModule;
+import org.sonar.api.batch.fs.internal.DefaultInputProject;
 import org.sonar.api.batch.fs.internal.SensorStrategy;
-import org.sonar.scanner.scan.DefaultInputModuleHierarchy;
 import org.sonar.scanner.scan.ScanProperties;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 public class InputFileBuilderTest {
   @Rule
@@ -59,6 +59,11 @@ public class InputFileBuilderTest {
       .setKey("root"), 0);
     Path moduleBaseDir = baseDir.resolve("module1");
     Files.createDirectories(moduleBaseDir);
+    DefaultInputProject project = new DefaultInputProject(ProjectDefinition.create()
+      .setBaseDir(baseDir.toFile())
+      .setWorkDir(workDir.toFile())
+      .setProperty(CoreProperties.ENCODING_PROPERTY, StandardCharsets.UTF_8.name())
+      .setKey("module1"), 0);
     DefaultInputModule module = new DefaultInputModule(ProjectDefinition.create()
       .setBaseDir(moduleBaseDir.toFile())
       .setWorkDir(workDir.toFile())
@@ -67,10 +72,8 @@ public class InputFileBuilderTest {
     MetadataGenerator metadataGenerator = mock(MetadataGenerator.class);
     ScannerComponentIdGenerator idGenerator = new ScannerComponentIdGenerator();
     ScanProperties properties = mock(ScanProperties.class);
-    ModuleFileSystemInitializer moduleFileSystemInitializer = mock(ModuleFileSystemInitializer.class);
-    when(moduleFileSystemInitializer.defaultEncoding()).thenReturn(StandardCharsets.UTF_8);
     sensorStrategy = new SensorStrategy();
-    builder = new InputFileBuilder(module, metadataGenerator, idGenerator, properties, moduleFileSystemInitializer, new DefaultInputModuleHierarchy(root),
+    builder = new InputFileBuilder(project, module, metadataGenerator, idGenerator, properties,
       sensorStrategy);
   }
 
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/scan/filesystem/ModuleFileSystemInitializerTest.java
deleted file mode 100644 (file)
index c56318c..0000000
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2019 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.scanner.scan.filesystem;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Path;
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.FilenameUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.bootstrap.ProjectDefinition;
-import org.sonar.api.batch.fs.internal.DefaultInputModule;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class ModuleFileSystemInitializerTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Test
-  public void test_default_directories() throws Exception {
-    File baseDir = temp.newFolder("base");
-    File workDir = temp.newFolder("work");
-    ProjectDefinition module = ProjectDefinition.create().setBaseDir(baseDir).setWorkDir(workDir);
-
-    ModuleFileSystemInitializer initializer = new ModuleFileSystemInitializer(new DefaultInputModule(module));
-
-    assertThat(logTester.logs(LoggerLevel.INFO)).contains("Base dir: " + baseDir.toPath().toAbsolutePath().toString());
-    assertThat(logTester.logs(LoggerLevel.INFO)).contains("Working dir: " + workDir.toPath().toAbsolutePath().toString());
-    assertThat(initializer.sources()).isEmpty();
-    assertThat(initializer.tests()).isEmpty();
-  }
-
-  @Test
-  public void should_init_directories() throws IOException {
-    File baseDir = temp.newFolder("base");
-    File workDir = temp.newFolder("work");
-    File sourceDir = new File(baseDir, "src/main/java");
-    FileUtils.forceMkdir(sourceDir);
-    File testDir = new File(baseDir, "src/test/java");
-    FileUtils.forceMkdir(testDir);
-    File binaryDir = new File(baseDir, "target/classes");
-    FileUtils.forceMkdir(binaryDir);
-
-    ProjectDefinition project = ProjectDefinition.create()
-      .setBaseDir(baseDir)
-      .setWorkDir(workDir)
-      .addSources("src/main/java", "src/main/unknown")
-      .addTests("src/test/java", "src/test/unknown");
-
-    ModuleFileSystemInitializer initializer = new ModuleFileSystemInitializer(new DefaultInputModule(project));
-
-    assertThat(initializer.sources()).hasSize(1);
-    assertThat(path(initializer.sources().get(0))).endsWith("src/main/java");
-    assertThat(initializer.tests()).hasSize(1);
-    assertThat(path(initializer.tests().get(0))).endsWith("src/test/java");
-  }
-
-  @Test
-  public void supportFilenamesWithComma() throws IOException {
-    File baseDir = temp.newFolder("base");
-    File workDir = temp.newFolder("work");
-    File sourceFile = new File(baseDir, "my,File.cs");
-    sourceFile.createNewFile();
-    File testFile = new File(baseDir, "my,TestFile.cs");
-    testFile.createNewFile();
-
-    ProjectDefinition project = ProjectDefinition.create()
-      .setBaseDir(baseDir)
-      .setWorkDir(workDir)
-      .addSources("\"my,File.cs\"")
-      .addTests("\"my,TestFile.cs\"");
-
-    ModuleFileSystemInitializer initializer = new ModuleFileSystemInitializer(new DefaultInputModule(project));
-
-    assertThat(initializer.sources()).hasSize(1);
-    assertThat(initializer.sources().get(0)).isEqualTo(sourceFile.toPath());
-    assertThat(initializer.tests()).hasSize(1);
-    assertThat(initializer.tests().get(0)).isEqualTo(testFile.toPath());
-  }
-
-  private String path(Path f) throws IOException {
-    return FilenameUtils.separatorsToUnix(f.toString());
-  }
-
-}