@Override
  public List<String> readFilesWithRetries(Sleeper sleeper, BackOff backOff)
      throws IOException, InterruptedException {
    IOException lastException = null;

    do {
      try {
        // Match inputPath which may contains glob
        Collection<Metadata> files =
            Iterables.getOnlyElement(FileSystems.match(Collections.singletonList(filePattern)))
                .metadata();

        LOG.debug("Found {} file(s) by matching the path: {}", files.size(), filePattern);

        if (files.isEmpty() || !checkTotalNumOfFiles(files)) {
          continue;
        }

        // Read data from file paths
        return readLines(files);
      } catch (IOException e) {
        // Ignore and retry
        lastException = e;
        LOG.warn("Error in file reading. Ignore and retry.");
      }
    } while (BackOffUtils.next(sleeper, backOff));
    // Failed after max retries
    throw new IOException(
        String.format("Unable to read file(s) after retrying %d times", MAX_READ_RETRIES),
        lastException);
  }
@Test
  public void testReadCustomTemplate() throws Exception {
    String contents1 = "To be or not to be, ", contents2 = "it is not a question.";

    // Customized template: resultSSS-totalNNN
    File tmpFile1 = tmpFolder.newFile("result0-total2");
    File tmpFile2 = tmpFolder.newFile("result1-total2");
    Files.asCharSink(tmpFile1, StandardCharsets.UTF_8).write(contents1);
    Files.asCharSink(tmpFile2, StandardCharsets.UTF_8).write(contents2);

    Pattern customizedTemplate =
        Pattern.compile("(?x) result (?<shardnum>\\d+) - total (?<numshards>\\d+)");
    NumberedShardedFile shardedFile = new NumberedShardedFile(filePattern, customizedTemplate);

    assertThat(shardedFile.readFilesWithRetries(), containsInAnyOrder(contents1, contents2));
  }