class STCClusteringAlgorithm extends AttrComposite implements ClusteringAlgorithm {

  @Override
  public <T extends Document> List<Cluster<T>> cluster(
      Stream<? extends T> docStream, LanguageComponents languageComponents) {
    List<T> documents = docStream.collect(Collectors.toList());
    List<Cluster<T>> clusters = new ArrayList<>();

    /*
     * Step 1. Preprocessing: tokenization, stop word marking and stemming (if available).
     */
    context = preprocessing.preprocess(documents.stream(), queryHint.get(), languageComponents);
    labelFormatter = context.languageComponents.get(LabelFormatter.class);

    /*
     * Step 2: Create a generalized suffix tree from phrases in the input.
     */
    sb = new GeneralizedSuffixTree.SequenceBuilder();

    final int[] tokenIndex = context.allTokens.wordIndex;
    final short[] tokenType = context.allTokens.type;
    for (int i = 0; i < tokenIndex.length; i++) {
      /* Advance until the first real token. */
      if (tokenIndex[i] == -1) {
        if ((tokenType[i] & (Tokenizer.TF_SEPARATOR_DOCUMENT | Tokenizer.TF_TERMINATOR)) != 0) {
          sb.endDocument();
        }
        continue;
      }

      /* We have the first token. Advance until non-token. */
      final int s = i;

      while (tokenIndex[i + 1] != -1) i++;
      final int phraseLength = 1 + i - s;
      if (phraseLength >= 1) {
        /* We have a phrase. */
        sb.addPhrase(tokenIndex, s, phraseLength);
      }
    }
    sb.buildSuffixTree();

    /*
     * Step 3: Find "base" clusters by looking up frequently recurring phrases in the
     * generalized suffix tree.
     */
    List<ClusterCandidate> baseClusters = createBaseClusters(sb);

    /*
     * Step 4: Merge base clusters that overlap too much to form final clusters.
     */
    List<ClusterCandidate> mergedClusters = createMergedClusters(baseClusters);

    /*
     * Step 5: Create the junk (unassigned documents) cluster and create the final
     * set of clusters in Carrot2 format.
     */
    postProcessing(documents, mergedClusters, clusters);

    return SharedInfrastructure.reorderByWeightedScoreAndSize(clusters, this.scoreWeight.get());
  }

}