class MixingSentenceSource extends SentenceSource {

  private final List<SentenceSource> sources;
  private final Map<String, Integer> sourceDistribution = new HashMap<>();
  
  private int count;

  public static MixingSentenceSource create(List<String> dumpFileNames, Language language) throws IOException {
    return create(dumpFileNames, language, null);
  }

  public static MixingSentenceSource create(List<String> dumpFileNames, Language language, Pattern filter) throws IOException {
    List<SentenceSource> sources = new ArrayList<>();
    for (String dumpFileName : dumpFileNames) {
      File file = new File(dumpFileName);
      if (file.getName().endsWith(".xml")) {
        sources.add(new WikipediaSentenceSource(new FileInputStream(dumpFileName), language, filter));
      } else if (file.getName().startsWith("tatoeba-")) {
        sources.add(new TatoebaSentenceSource(new FileInputStream(dumpFileName), language, filter));
      } else if (file.getName().endsWith(".txt")) {
        sources.add(new PlainTextSentenceSource(new FileInputStream(dumpFileName), language, filter));
      } else if (file.getName().endsWith(".xz")) {
        sources.add(new CommonCrawlSentenceSource(new FileInputStream(dumpFileName), language, filter));
      } else {
        throw new RuntimeException("Could not find a source handler for " + dumpFileName +
                " - Wikipedia files must be named '*.xml', Tatoeba files must be named 'tatoeba-*', CommonCrawl files '*.xz', plain text files '*.txt'");
      }
    }
    return new MixingSentenceSource(sources, language);
  }

  private MixingSentenceSource(List<SentenceSource> sources, Language language) {
    super(language);
    this.sources = sources;
  }

  Map<String, Integer> getSourceDistribution() {
    return sourceDistribution;
  }
  
  @Override
  public boolean hasNext() {<FILL_FUNCTION_BODY>}

  @Override
  public Sentence next() {
    SentenceSource sentenceSource = sources.get(count % sources.size());
    while (!sentenceSource.hasNext()) {
      sources.remove(sentenceSource);
      if (sources.isEmpty()) {
        throw new NoSuchElementException();
      }
      count++;
      sentenceSource = sources.get(count % sources.size());
    }
    count++;
    Sentence next = sentenceSource.next();
    updateDistributionMap(next);
    return next;
  }

  private void updateDistributionMap(Sentence next) {
    Integer prevCount = sourceDistribution.get(next.getSource());
    if (prevCount != null) {
      sourceDistribution.put(next.getSource(), prevCount + 1);
    } else {
      sourceDistribution.put(next.getSource(), 1);
    }
  }

  @Override
  public String getSource() {
    return StringUtils.join(sources, ", ");
  }

  int getIgnoredCount() {
    int sum = 0;
    for (SentenceSource source : sources) {
      sum += source.getIgnoredCount();
    }
    return sum;
  }

}

    for (SentenceSource source : sources) {
      if (source.hasNext()) {
        return true;
      }
    }
    return false;
  
<methods>public abstract java.lang.String getSource() ,public abstract boolean hasNext() ,public abstract org.languagetool.dev.dumpcheck.Sentence next() ,public void remove() ,public java.lang.String toString() <variables>static final int MAX_SENTENCE_LENGTH,static final int MIN_SENTENCE_LENGTH,static final int MIN_SENTENCE_TOKEN_COUNT,private final non-sealed java.util.regex.Pattern acceptPattern,private int ignoreCount,private final non-sealed org.languagetool.tokenizers.Tokenizer wordTokenizer