com.hazelcast.shaded.org.apache.calcite.util.Puffin Maven / Gradle / Ivy
/*
* 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 com.hazelcast.shaded.org.apache.calcite.util;
import com.hazelcast.shaded.org.apache.calcite.runtime.PairList;
import com.hazelcast.shaded.org.apache.calcite.runtime.Unit;
import com.hazelcast.shaded.com.google.common.cache.CacheBuilder;
import com.hazelcast.shaded.com.google.common.cache.CacheLoader;
import com.hazelcast.shaded.com.google.common.cache.LoadingCache;
import com.hazelcast.shaded.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.Pattern;
import java.util.stream.Stream;
import static java.util.Objects.requireNonNull;
/**
* A text processor similar to Awk.
*
* Example use:
*
*
{@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);
* }
*
* prints the following to stdout:
*
*
{@code
* There were 3 uncommented lines.
* }
*/
public class Puffin {
private Puffin() {
}
/** Creates a Builder.
*
* @param fileStateFactory Creates the state for each file
* @return Builder
* @param Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file
*/
public static Builder builder(Supplier globalStateFactory,
Function 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 builder() {
return builder(() -> Unit.INSTANCE, u -> u);
}
/** Fluent interface for constructing a Program.
*
* @param Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file
* @see Puffin#builder */
public interface Builder {
/** Adds a predicate and action to be invoked on each line of a source. */
Builder add(Predicate> linePredicate,
Consumer> action);
/** Adds an action to be called before each source. */
Builder beforeSource(Consumer> action);
/** Adds an action to be called after each source. */
Builder afterSource(Consumer> action);
/** Adds an action to be called before all sources. */
Builder before(Consumer> action);
/** Adds an action to be called after all sources. */
Builder after(Consumer> action);
/** Builds the program. */
Program build();
}
/** A Puffin program. You can execute it on a file.
*
* @param Type of state that is created when we start processing */
public interface Program {
/** 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.
*
* Created by an executing program and passed to the predicate
* and action that you registered in
* {@link Builder#add(Predicate, Consumer)}.
*
* @param Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file
*/
public interface Line {
G globalState();
F state();
int fnr();
String filename();
Source source();
boolean startsWith(String prefix);
boolean contains(CharSequence s);
boolean endsWith(String suffix);
boolean matches(String regex);
String line();
}
/** Context for executing a Puffin program within a given file.
*
* @param Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file */
public static class Context {
final PrintWriter out;
final Source source;
final F fileState;
final G globalState;
private final Function patternCache;
/** Holds the current line. */
String line = "";
/** Holds the current line number in the file (starting from 1).
*
* Corresponds to the Awk variable {@code FNR}, which stands for "file
* number of records". */
int fnr = 0;
Context(PrintWriter out, Source source,
Function 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}.
*
* 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 Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file */
static class ContextImpl extends Context implements Line {
ContextImpl(PrintWriter out, Source source,
Function patternCache, G globalState, F state) {
super(out, source, patternCache, globalState, state);
}
@Override public int fnr() {
return fnr;
}
@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 boolean matches(String regex) {
return pattern(regex).matcher(line).matches();
}
@Override public String line() {
return line;
}
}
/** Implementation of {@link Program}.
*
* @param Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file */
private static class ProgramImpl implements Program {
private final Supplier globalStateFactory;
private final Function fileStateFactory;
private final PairList>, Consumer>> onLineList;
private final ImmutableList>> beforeSourceList;
private final ImmutableList>> afterSourceList;
private final ImmutableList>> beforeList;
private final ImmutableList>> afterList;
@SuppressWarnings("Convert2MethodRef")
private final LoadingCache patternCache0 =
CacheBuilder.newBuilder()
.build(CacheLoader.from(regex -> Pattern.compile(regex)));
private final Function patternCache =
patternCache0::getUnchecked;
private ProgramImpl(Supplier globalStateFactory,
Function fileStateFactory,
PairList>, Consumer>> onLineList,
ImmutableList>> beforeSourceList,
ImmutableList>> afterSourceList,
ImmutableList>> beforeList,
ImmutableList>> 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 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 x =
new ContextImpl(out, source, patternCache, globalState,
fileState);
beforeSourceList.forEach(action -> action.accept(x));
for (;;) {
String lineText = br.readLine();
if (lineText == null) {
break;
}
++x.fnr;
x.line = lineText;
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 Type of state that is created when we start processing
* @param Type of state that is created when we start processing a file */
private static class BuilderImpl implements Builder {
private final Supplier globalStateFactory;
private final Function fileStateFactory;
final PairList>, Consumer>> onLineList;
final List>> beforeSourceList;
final List>> afterSourceList;
final List>> beforeList;
final List>> afterList;
private BuilderImpl(Supplier globalStateFactory,
Function fileStateFactory,
PairList>, Consumer>> onLineList,
List>> beforeSourceList,
List>> afterSourceList,
List>> beforeList,
List>> 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 add(Predicate> linePredicate,
Consumer> action) {
onLineList.add(linePredicate, action);
return this;
}
@Override public Builder beforeSource(Consumer> action) {
beforeSourceList.add(action);
return this;
}
@Override public Builder afterSource(Consumer> action) {
afterSourceList.add(action);
return this;
}
@Override public Builder before(Consumer> action) {
beforeList.add(action);
return this;
}
@Override public Builder after(Consumer> action) {
afterList.add(action);
return this;
}
@Override public Program build() {
return new ProgramImpl<>(globalStateFactory, fileStateFactory,
onLineList.immutable(),
ImmutableList.copyOf(beforeSourceList),
ImmutableList.copyOf(afterSourceList),
ImmutableList.copyOf(beforeList),
ImmutableList.copyOf(afterList));
}
}
}