content: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to you under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.calcite.util;
  
  import org.apache.calcite.runtime.PairList;
  import org.apache.calcite.runtime.Unit;
  
  import com.google.common.cache.CacheBuilder;
  import com.google.common.cache.CacheLoader;
  import com.google.common.cache.LoadingCache;
  import com.google.common.collect.ImmutableList;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.PrintWriter;
  import java.io.Reader;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.function.Consumer;
  import java.util.function.Function;
  import java.util.function.Predicate;
  import java.util.function.Supplier;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  import java.util.stream.Stream;
  
  import static java.util.Objects.requireNonNull;
  
  /**
   * A text processor similar to Awk.
   *
   * <p>Example use:
   *
   * <blockquote><pre>{@code
   * File file;
   * final Puffin.Program program =
   *   Puffin.builder()
   *       .add(line -> !line.startsWith("#"),
   *           line -> counter.incrementAndGet())
   *       .after(context ->
   *           context.println("There were " + counter.get()
   *               + " uncommented lines"))
   *       .build();
   * program.execute(Source.of(file), System.out);
   * }</pre></blockquote>
   *
   * <p>prints the following to stdout:
   *
   * <blockquote>{@code
   * There were 3 uncommented lines.
   * }</blockquote>
   */
  public class Puffin {
    private Puffin() {
    }
  
    /** Creates a Builder.
     *
     * @param fileStateFactory Creates the state for each file
     * @return Builder
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file
     */
    public static <G, F> Builder<G, F> builder(Supplier<G> globalStateFactory,
        Function<G, F> fileStateFactory) {
      return new BuilderImpl<>(globalStateFactory, fileStateFactory,
          PairList.of(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(),
          new ArrayList<>());
    }
  
    /** Creates a Builder with no state. */
    public static Builder<Unit, Unit> builder() {
      return builder(() -> Unit.INSTANCE, u -> u);
    }
  
    /** Fluent interface for constructing a Program.
     *
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file
     * @see Puffin#builder */
    public interface Builder<G, F> {
      /** Adds a predicate and action to be invoked on each line of a source. */
      Builder<G, F> add(Predicate<Line<G, F>> linePredicate,
          Consumer<Line<G, F>> action);
  
      /** Adds an action to be called before each source. */
      Builder<G, F> beforeSource(Consumer<Context<G, F>> action);
  
      /** Adds an action to be called after each source. */
      Builder<G, F> afterSource(Consumer<Context<G, F>> action);
  
      /** Adds an action to be called before all sources. */
      Builder<G, F> before(Consumer<Context<G, F>> action);
  
      /** Adds an action to be called after all sources. */
      Builder<G, F> after(Consumer<Context<G, F>> action);
  
      /** Builds the program. */
      Program<G> build();
    }
  
    /** A Puffin program. You can execute it on a file.
     *
     * @param <G> Type of state that is created when we start processing */
    public interface Program<G> {
      /** Executes this program. */
      G execute(Stream<? extends Source> sources, PrintWriter out);
  
      /** Executes this program, writing to an output stream such as
       * {@link System#out}. */
      default void execute(Stream<? extends Source> sources, OutputStream out) {
        try (PrintWriter w = Util.printWriter(out)) {
          execute(sources, w);
        }
      }
  
      /** Executes this program on a single source. */
      default void execute(Source source, OutputStream out) {
        execute(Stream.of(source), out);
      }
    }
  
    /** A line in a file.
     *
     * <p>Created by an executing program and passed to the predicate
     * and action that you registered in
     * {@link Builder#add(Predicate, Consumer)}.
     *
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file
     */
    public interface Line<G, F> {
      /** Returns the global state. */
      G globalState();
  
      /** Returns the state of the current file. */
      F state();
  
      /** Returns the ordinal of the current line in its source. */
      int fnr();
  
      /** Returns the filename of the current source. */
      String filename();
  
      /** Returns the current source. */
      Source source();
  
      /** Returns whether the current line starts with {@code prefix}. */
      boolean startsWith(String prefix);
  
      /** Returns whether the current line starts with {@code seek}. */
      boolean contains(CharSequence seek);
  
      /** Returns whether the current line ends with {@code suffix}. */
      boolean endsWith(String suffix);
  
      /** Returns whether the current line matches {@code regex}. */
      boolean matches(String regex);
  
      Matcher matcher(String regex);
  
      /** Returns the text of the current line. */
      String line();
  
      /** Returns whether the current line is the last line in its source. */
      boolean isLast();
    }
  
    /** Context for executing a Puffin program within a given file.
     *
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file */
    public static class Context<G, F> {
      final PrintWriter out;
      final Source source;
      final F fileState;
      final G globalState;
      private final Function<String, Pattern> patternCache;
  
      /** Holds the current line. */
      String line = "";
  
      /** Holds the current line number in the file (starting from 1).
       *
       * <p>Corresponds to the Awk variable {@code FNR}, which stands for "file
       * number of records". */
      int fnr = 0;
  
      /** Holds whether the current line is the last line in its source. */
      boolean last;
  
      Context(PrintWriter out, Source source,
          Function<String, Pattern> patternCache, G globalState,
          F fileState) {
        this.out = requireNonNull(out, "out");
        this.source = requireNonNull(source, "source");
        this.patternCache = requireNonNull(patternCache, "patternCache");
        this.globalState = requireNonNull(globalState, "globalState");
        this.fileState = requireNonNull(fileState, "fileState");
      }
  
      public F state() {
        return fileState;
      }
  
      public G globalState() {
        return globalState;
      }
  
      public void println(String s) {
        out.println(s);
      }
  
      Pattern pattern(String regex) {
        return patternCache.apply(regex);
      }
    }
  
    /** Extension to {@link Context} that also implements {@link Line}.
     *
     * <p>We don't want clients to know that {@code Context} implements
     * {@code Line}, but neither do we want to create a new {@code Line} object
     * for every line in the file. Making this a subclass accomplishes both
     * goals.
     *
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file */
    static class ContextImpl<G, F> extends Context<G, F> implements Line<G, F> {
  
      ContextImpl(PrintWriter out, Source source,
          Function<String, Pattern> patternCache, G globalState, F state) {
        super(out, source, patternCache, globalState, state);
      }
  
      @Override public int fnr() {
        return fnr;
      }
  
      @Override public boolean isLast() {
        return last;
      }
  
      @Override public String filename() {
        return source().toString();
      }
  
      @Override public Source source() {
        return source;
      }
  
      @Override public boolean startsWith(String prefix) {
        return line.startsWith(prefix);
      }
  
      @Override public boolean contains(CharSequence s) {
        return line.contains(s);
      }
  
      @Override public boolean endsWith(String suffix) {
        return line.endsWith(suffix);
      }
  
      @Override public Matcher matcher(String regex) {
        return pattern(regex).matcher(line);
      }
  
      @Override public boolean matches(String regex) {
        return matcher(regex).matches();
      }
  
      @Override public String line() {
        return line;
      }
    }
  
    /** Implementation of {@link Program}.
     *
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file */
    private static class ProgramImpl<G, F> implements Program<G> {
      private final Supplier<G> globalStateFactory;
      private final Function<G, F> fileStateFactory;
      private final PairList<Predicate<Line<G, F>>, Consumer<Line<G, F>>> onLineList;
      private final ImmutableList<Consumer<Context<G, F>>> beforeSourceList;
      private final ImmutableList<Consumer<Context<G, F>>> afterSourceList;
      private final ImmutableList<Consumer<Context<G, F>>> beforeList;
      private final ImmutableList<Consumer<Context<G, F>>> afterList;
      @SuppressWarnings("Convert2MethodRef")
      private final LoadingCache<String, Pattern> patternCache0 =
          CacheBuilder.newBuilder()
              .build(CacheLoader.from(regex -> Pattern.compile(regex)));
      private final Function<String, Pattern> patternCache =
          patternCache0::getUnchecked;
  
      private ProgramImpl(Supplier<G> globalStateFactory,
          Function<G, F> fileStateFactory,
          PairList<Predicate<Line<G, F>>, Consumer<Line<G, F>>> onLineList,
          ImmutableList<Consumer<Context<G, F>>> beforeSourceList,
          ImmutableList<Consumer<Context<G, F>>> afterSourceList,
          ImmutableList<Consumer<Context<G, F>>> beforeList,
          ImmutableList<Consumer<Context<G, F>>> afterList) {
        this.globalStateFactory = globalStateFactory;
        this.fileStateFactory = fileStateFactory;
        this.onLineList = onLineList;
        this.beforeSourceList = beforeSourceList;
        this.afterSourceList = afterSourceList;
        this.beforeList = beforeList;
        this.afterList = afterList;
      }
  
      @Override public G execute(Stream<? extends Source> sources,
          PrintWriter out) {
        final G globalState = globalStateFactory.get();
        final Source source0 = Sources.of("");
        final F fileState0 = fileStateFactory.apply(globalState);
        final ContextImpl<G, F> x0 =
            new ContextImpl<>(out, source0, patternCache, globalState,
                fileState0);
        beforeList.forEach(action -> action.accept(x0));
        sources.forEach(source -> execute(globalState, source, out));
        afterList.forEach(action -> action.accept(x0));
        return globalState;
      }
  
      private void execute(G globalState, Source source, PrintWriter out) {
        try (Reader r = source.reader();
             BufferedReader br = new BufferedReader(r)) {
          final F fileState = fileStateFactory.apply(globalState);
          final ContextImpl<G, F> x =
              new ContextImpl<>(out, source, patternCache, globalState,
                  fileState);
          beforeSourceList.forEach(action -> action.accept(x));
          String nextLine = br.readLine();
          while (nextLine != null) {
            x.line = nextLine;
            ++x.fnr;
            nextLine = br.readLine();
            x.last = nextLine == null;
            onLineList.forEach((predicate, action) -> {
              if (predicate.test(x)) {
                action.accept(x);
              }
            });
          }
          afterSourceList.forEach(action -> action.accept(x));
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }
  
  
    /** Implementation of Builder.
     *
     * @param <G> Type of state that is created when we start processing
     * @param <F> Type of state that is created when we start processing a file */
    private static class BuilderImpl<G, F> implements Builder<G, F> {
      private final Supplier<G> globalStateFactory;
      private final Function<G, F> fileStateFactory;
      final PairList<Predicate<Line<G, F>>, Consumer<Line<G, F>>> onLineList;
      final List<Consumer<Context<G, F>>> beforeSourceList;
      final List<Consumer<Context<G, F>>> afterSourceList;
      final List<Consumer<Context<G, F>>> beforeList;
      final List<Consumer<Context<G, F>>> afterList;
  
      private BuilderImpl(Supplier<G> globalStateFactory,
          Function<G, F> fileStateFactory,
          PairList<Predicate<Line<G, F>>, Consumer<Line<G, F>>> onLineList,
          List<Consumer<Context<G, F>>> beforeSourceList,
          List<Consumer<Context<G, F>>> afterSourceList,
          List<Consumer<Context<G, F>>> beforeList,
          List<Consumer<Context<G, F>>> afterList) {
        this.globalStateFactory = globalStateFactory;
        this.fileStateFactory = fileStateFactory;
        this.onLineList = onLineList;
        this.beforeSourceList = beforeSourceList;
        this.afterSourceList = afterSourceList;
        this.beforeList = beforeList;
        this.afterList = afterList;
      }
  
      @Override public Builder<G, F> add(Predicate<Line<G, F>> linePredicate,
          Consumer<Line<G, F>> action) {
        onLineList.add(linePredicate, action);
        return this;
      }
  
      @Override public Builder<G, F> beforeSource(Consumer<Context<G, F>> action) {
        beforeSourceList.add(action);
        return this;
      }
  
      @Override public Builder<G, F> afterSource(Consumer<Context<G, F>> action) {
        afterSourceList.add(action);
        return this;
      }
  
      @Override public Builder<G, F> before(Consumer<Context<G, F>> action) {
        beforeList.add(action);
        return this;
      }
  
      @Override public Builder<G, F> after(Consumer<Context<G, F>> action) {
        afterList.add(action);
        return this;
      }
  
      @Override public Program<G> build() {
        return new ProgramImpl<>(globalStateFactory, fileStateFactory,
            onLineList.immutable(),
            ImmutableList.copyOf(beforeSourceList),
            ImmutableList.copyOf(afterSourceList),
            ImmutableList.copyOf(beforeList),
            ImmutableList.copyOf(afterList));
      }
    }
  }
  