StableTemplatingUtils.java

package com.github.dakusui.actionunit.utils;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.github.dakusui.actionunit.utils.Checks.requireArgument;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;

public enum StableTemplatingUtils {
  ;
  public static String template(String template, Map<String, Object> mapping) {
    AtomicInteger lastPosition = new AtomicInteger(0);
    StringBuilder b = new StringBuilder();
    positionToPlaceHolder(template, mapping.keySet())
        .forEach(chainBiConsumers(
            (Integer position, String placeHolder) -> b.append(
                template,
                lastPosition.get(),
                position),
            (Integer position, String placeHolder) -> b.append(mapping.get(placeHolder)),
            (Integer position, String placeHolder) -> lastPosition.accumulateAndGet(
                placeHolder.length(),
                (i, j) -> position + j)
        ));
    if (lastPosition.get() < template.length())
      b.append(template, lastPosition.get(), template.length());
    return b.toString();
  }

  public static SortedMap<String, Object> toMapping(IntFunction<String> placeHolderComposer, Object[] argValues) {
    AtomicInteger i = new AtomicInteger();
    return parameterPlaceHolders(placeHolderComposer, argValues.length)
        .stream()
        .collect(toLinkedHashMap(placeHolder -> placeHolder, placeHolder -> argValues[i.getAndIncrement()]));
  }

  static List<String> parameterPlaceHolders(IntFunction<String> placeHolderComposer, int numParameters) {
    return IntStream.range(0, numParameters)
        .mapToObj(placeHolderComposer)
        .collect(toList());
  }

  static SortedMap<Integer, String> positionToPlaceHolder(String formatString, Collection<String> placeHolders) {
    return new TreeMap<Integer, String>() {{
      for (Optional<PositionedPlaceHolder> positionedPlaceHolderOptional = findFirstPlaceHolderFrom(
          formatString,
          0,
          new LinkedList<>(placeHolders));
           positionedPlaceHolderOptional.isPresent();
           positionedPlaceHolderOptional = findFirstPlaceHolderFrom(
               formatString,
               positionedPlaceHolderOptional.get().position + positionedPlaceHolderOptional.get().placeHolder.length(),
               new LinkedList<>(placeHolders)
           )) {
        PositionedPlaceHolder positionedPlaceHolder = positionedPlaceHolderOptional.get();
        this.put(positionedPlaceHolder.position, positionedPlaceHolder.placeHolder);
      }
    }};
  }

  private static Optional<PositionedPlaceHolder> findFirstPlaceHolderFrom(
      String template,
      int from,
      Collection<String> remainingPlaceHolders) {
    String templateSubString = template.substring(from);
    List<String> notFound = new LinkedList<>();
    AtomicReference<Optional<PositionedPlaceHolder>> ret = new AtomicReference<>(Optional.empty());
    remainingPlaceHolders.forEach(
        (String s) -> {
          int position = templateSubString.indexOf(s);
          if (position < 0)
            notFound.add(s);
          else {
            PositionedPlaceHolder found = PositionedPlaceHolder.of(s, from + position);
            if (!ret.get().isPresent() || found.position < ret.get().get().position)
              ret.set(Optional.of(found));
          }
        });
    remainingPlaceHolders.removeAll(notFound);
    return ret.get();
  }


  private static <T, K, U> Collector<T, ?, SortedMap<K, U>> toLinkedHashMap(Function<? super T, ? extends K> keyMapper,
      Function<? super T, ? extends U> valueMapper) {
    return Collectors.toMap(keyMapper, valueMapper, throwingMerger(), TreeMap::new);
  }

  private static <T> BinaryOperator<T> throwingMerger() {
    return (u, v) -> {
      throw new IllegalStateException(String.format("Duplicate key %s", u));
    };
  }

  @SafeVarargs
  private static <T, U> BiConsumer<T, U> chainBiConsumers(BiConsumer<T, U> c1, BiConsumer<T, U>... c2) {
    BiConsumer<T, U> ret = c1;
    for (BiConsumer<T, U> each : c2)
      ret = ret.andThen(each);
    return ret;
  }

  private static class PositionedPlaceHolder {
    final String placeHolder;
    final int    position;

    private PositionedPlaceHolder(String placeHolder, int position) {
      requireArgument(s -> s.length() > 0, requireNonNull(placeHolder));
      requireArgument(i -> i >= 0, position);
      this.placeHolder = placeHolder;
      this.position = position;
    }

    static PositionedPlaceHolder of(String placeHolder, int position) {
      return new PositionedPlaceHolder(placeHolder, position);
    }
  }
}