code: public List<FileInfo> getFileInfos(List<URI> paths) throws IOException {
      checkArgument(paths != null, "paths must not be null");
      logger.atFine().log("getFileInfos(%d paths)", paths.size());
  
      if (paths.size() == 1) {
        return new ArrayList<>(Collections.singleton(getFileInfo(paths.get(0))));
      }
  
      int maxThreads = gcs.getOptions().getBatchThreads();
      ExecutorService fileInfoExecutor =
          maxThreads == 0
              ? MoreExecutors.newDirectExecutorService()
              : Executors.newFixedThreadPool(
                  Math.min(maxThreads, paths.size()), DAEMON_THREAD_FACTORY);
      try {
        List<Future<FileInfo>> infoFutures = new ArrayList<>(paths.size());
        for (URI path : paths) {
          infoFutures.add(fileInfoExecutor.submit(() -> getFileInfo(path)));
        }
        fileInfoExecutor.shutdown();
  
        List<FileInfo> infos = new ArrayList<>(paths.size());
        for (Future<FileInfo> infoFuture : infoFutures) {
          try {
            infos.add(infoFuture.get());
          } catch (InterruptedException | ExecutionException e) {
            if (e instanceof InterruptedException) {
              Thread.currentThread().interrupt();
            }
            throw new IOException(
                String.format("Failed to getFileInfos for %d paths", paths.size()), e);
          }
        }
        return infos;
      } finally {
        fileInfoExecutor.shutdownNow();
      }
    }
docs: Gets information about each path in the given list; more efficient than calling getFileInfo()
  on each path individually in a loop.
  
  @param paths List of paths.
  @return Information about each path in the given list.
  @throws IOException