| /* |
| * 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.runtime; |
| |
| import org.apache.calcite.linq4j.MemoryFactory; |
| import org.apache.calcite.util.ImmutableBitSet; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Workspace that partialMatches patterns against an automaton. |
| * |
| * @param <E> Type of rows matched by this automaton |
| */ |
| public class Matcher<E> { |
| private final DeterministicAutomaton dfa; |
| private final ImmutableMap<String, Predicate<MemoryFactory.Memory<E>>> predicates; |
| |
| // The following members are work space. They can be shared among partitions, |
| // but only one thread can use them at a time. Putting them here saves the |
| // expense of creating a fresh object each call to "match". |
| |
| @SuppressWarnings("unused") |
| private final ImmutableBitSet startSet; |
| |
| /** |
| * Creates a Matcher; use {@link #builder}. |
| */ |
| private Matcher(Automaton automaton, |
| ImmutableMap<String, Predicate<MemoryFactory.Memory<E>>> predicates) { |
| this.predicates = Objects.requireNonNull(predicates, "predicates"); |
| final ImmutableBitSet.Builder startSetBuilder = |
| ImmutableBitSet.builder(); |
| startSetBuilder.set(automaton.startState.id); |
| automaton.epsilonSuccessors(automaton.startState.id, startSetBuilder); |
| startSet = startSetBuilder.build(); |
| // Build the DFA |
| dfa = new DeterministicAutomaton(automaton); |
| } |
| |
| public static <E> Builder<E> builder(Automaton automaton) { |
| return new Builder<>(automaton); |
| } |
| |
| public List<PartialMatch<E>> match(E... rows) { |
| return match(Arrays.asList(rows)); |
| } |
| |
| public List<PartialMatch<E>> match(Iterable<E> rows) { |
| final ImmutableList.Builder<PartialMatch<E>> resultMatchBuilder = |
| ImmutableList.builder(); |
| final Consumer<PartialMatch<E>> resultMatchConsumer = resultMatchBuilder::add; |
| final PartitionState<E> partitionState = createPartitionState(0, 0); |
| for (E row : rows) { |
| partitionState.getMemoryFactory().add(row); |
| matchOne(partitionState.getRows(), partitionState, resultMatchConsumer); |
| } |
| return resultMatchBuilder.build(); |
| } |
| |
| public PartitionState<E> createPartitionState(int history, int future) { |
| return new PartitionState<>(history, future); |
| } |
| |
| /** |
| * Feeds a single input row into the given partition state, |
| * and writes the resulting output rows (if any). |
| * This method ignores the symbols that caused a transition. |
| */ |
| protected void matchOne(MemoryFactory.Memory<E> rows, |
| PartitionState<E> partitionState, Consumer<PartialMatch<E>> resultMatches) { |
| List<PartialMatch<E>> matches = matchOneWithSymbols(rows, partitionState); |
| for (PartialMatch<E> pm : matches) { |
| resultMatches.accept(pm); |
| } |
| } |
| |
| protected List<PartialMatch<E>> matchOneWithSymbols(MemoryFactory.Memory<E> rows, |
| PartitionState<E> partitionState) { |
| final HashSet<PartialMatch<E>> newMatches = new HashSet<>(); |
| for (Map.Entry<String, Predicate<MemoryFactory.Memory<E>>> predicate |
| : predicates.entrySet()) { |
| for (PartialMatch<E> pm : partitionState.getPartialMatches()) { |
| // Remove this match |
| if (predicate.getValue().test(rows)) { |
| // Check if we have transitions from here |
| final List<DeterministicAutomaton.Transition> transitions = |
| dfa.getTransitions().stream() |
| .filter(t -> predicate.getKey().equals(t.symbol)) |
| .filter(t -> pm.currentState.equals(t.fromState)) |
| .collect(Collectors.toList()); |
| |
| for (DeterministicAutomaton.Transition transition : transitions) { |
| // System.out.println("Append new transition to "); |
| final PartialMatch<E> newMatch = |
| pm.append(transition.symbol, rows.get(), transition.toState); |
| newMatches.add(newMatch); |
| } |
| } |
| } |
| // Check if a new Match starts here |
| if (predicate.getValue().test(rows)) { |
| final List<DeterministicAutomaton.Transition> transitions = |
| dfa.getTransitions().stream() |
| .filter(t -> predicate.getKey().equals(t.symbol)) |
| .filter(t -> dfa.startState.equals(t.fromState)) |
| .collect(Collectors.toList()); |
| |
| for (DeterministicAutomaton.Transition transition : transitions) { |
| final PartialMatch<E> newMatch = |
| new PartialMatch<>(-1L, ImmutableList.of(transition.symbol), |
| ImmutableList.of(rows.get()), transition.toState); |
| newMatches.add(newMatch); |
| } |
| } |
| } |
| |
| // Remove all current partitions |
| partitionState.clearPartitions(); |
| // Add all partial matches |
| partitionState.addPartialMatches(newMatches); |
| // Check if one of the new Matches is in a final state, otherwise add them |
| // and go on |
| final ImmutableList.Builder<PartialMatch<E>> builder = |
| ImmutableList.builder(); |
| for (PartialMatch<E> match : newMatches) { |
| if (dfa.getEndStates().contains(match.currentState)) { |
| // This is the match, handle all "open" partial matches with a suitable |
| // strategy |
| // TODO add strategy |
| // Return it! |
| builder.add(match); |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * State for each partition. |
| * |
| * @param <E> Row type |
| */ |
| static class PartitionState<E> { |
| private final Set<PartialMatch<E>> partialMatches = new HashSet<>(); |
| private final MemoryFactory<E> memoryFactory; |
| |
| PartitionState(int history, int future) { |
| this.memoryFactory = new MemoryFactory<>(history, future); |
| } |
| |
| public void addPartialMatches(Collection<PartialMatch<E>> matches) { |
| partialMatches.addAll(matches); |
| } |
| |
| public Set<PartialMatch<E>> getPartialMatches() { |
| return ImmutableSet.copyOf(partialMatches); |
| } |
| |
| public void removePartialMatch(PartialMatch<E> pm) { |
| partialMatches.remove(pm); |
| } |
| |
| public void clearPartitions() { |
| partialMatches.clear(); |
| } |
| |
| public MemoryFactory.Memory<E> getRows() { |
| return memoryFactory.create(); |
| } |
| |
| public MemoryFactory<E> getMemoryFactory() { |
| return this.memoryFactory; |
| } |
| } |
| |
| /** |
| * Partial match of the NFA. |
| * |
| * <p>This class is immutable; the {@link #copy()} and |
| * {@link #append(String, Object, DeterministicAutomaton.MultiState)} |
| * methods generate new instances. |
| * |
| * @param <E> Row type |
| */ |
| static class PartialMatch<E> { |
| final long startRow; |
| final ImmutableList<String> symbols; |
| final ImmutableList<E> rows; |
| final DeterministicAutomaton.MultiState currentState; |
| |
| PartialMatch(long startRow, ImmutableList<String> symbols, |
| ImmutableList<E> rows, DeterministicAutomaton.MultiState currentState) { |
| this.startRow = startRow; |
| this.symbols = symbols; |
| this.rows = rows; |
| this.currentState = currentState; |
| } |
| |
| public PartialMatch<E> copy() { |
| return new PartialMatch<>(startRow, symbols, rows, currentState); |
| } |
| |
| public PartialMatch<E> append(String symbol, E row, |
| DeterministicAutomaton.MultiState toState) { |
| ImmutableList<String> symbols = ImmutableList.<String>builder() |
| .addAll(this.symbols) |
| .add(symbol) |
| .build(); |
| ImmutableList<E> rows = ImmutableList.<E>builder() |
| .addAll(this.rows) |
| .add(row) |
| .build(); |
| return new PartialMatch<>(startRow, symbols, rows, toState); |
| } |
| |
| @Override public boolean equals(@Nullable Object o) { |
| return o == this |
| || o instanceof PartialMatch |
| && startRow == ((PartialMatch) o).startRow |
| && Objects.equals(symbols, ((PartialMatch) o).symbols) |
| && Objects.equals(rows, ((PartialMatch) o).rows) |
| && Objects.equals(currentState, ((PartialMatch) o).currentState); |
| } |
| |
| @Override public int hashCode() { |
| return Objects.hash(startRow, symbols, rows, currentState); |
| } |
| |
| @Override public String toString() { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("["); |
| for (int i = 0; i < rows.size(); i++) { |
| if (i > 0) { |
| sb.append(", "); |
| } |
| sb.append("("); |
| sb.append(symbols.get(i)); |
| sb.append(", "); |
| sb.append(rows.get(i)); |
| sb.append(")"); |
| } |
| sb.append("]"); |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * Builds a Matcher. |
| * |
| * @param <E> Type of rows matched by this automaton |
| */ |
| public static class Builder<E> { |
| final Automaton automaton; |
| final Map<String, Predicate<MemoryFactory.Memory<E>>> symbolPredicates = |
| new HashMap<>(); |
| |
| Builder(Automaton automaton) { |
| this.automaton = automaton; |
| } |
| |
| /** |
| * Associates a predicate with a symbol. |
| */ |
| public Builder<E> add(String symbolName, |
| Predicate<MemoryFactory.Memory<E>> predicate) { |
| symbolPredicates.put(symbolName, predicate); |
| return this; |
| } |
| |
| public Matcher<E> build() { |
| final Set<String> predicateSymbolsNotInGraph = |
| Sets.newTreeSet(symbolPredicates.keySet()); |
| predicateSymbolsNotInGraph.removeAll(automaton.symbolNames); |
| if (!predicateSymbolsNotInGraph.isEmpty()) { |
| throw new IllegalArgumentException("not all predicate symbols [" |
| + predicateSymbolsNotInGraph + "] are in graph [" |
| + automaton.symbolNames + "]"); |
| } |
| final ImmutableMap.Builder<String, Predicate<MemoryFactory.Memory<E>>> builder = |
| ImmutableMap.builder(); |
| for (String symbolName : automaton.symbolNames) { |
| // If a symbol does not have a predicate, it defaults to true. |
| // By convention, "STRT" is used for the start symbol, but it could be |
| // anything. |
| builder.put(symbolName, |
| symbolPredicates.getOrDefault(symbolName, e -> true)); |
| } |
| return new Matcher<>(automaton, builder.build()); |
| } |
| } |
| |
| /** |
| * A 2-tuple consisting of a symbol and a row. |
| * |
| * @param <E> Type of Row |
| */ |
| static class Tuple<E> { |
| final String symbol; |
| final E row; |
| |
| Tuple(String symbol, E row) { |
| this.symbol = symbol; |
| this.row = row; |
| } |
| |
| @Override public boolean equals(@Nullable Object o) { |
| return o == this |
| || o instanceof Tuple |
| && ((Tuple) o).symbol.equals(symbol) |
| && Objects.equals(row, ((Tuple) o).row); |
| } |
| |
| @Override public int hashCode() { |
| return Objects.hash(symbol, row); |
| } |
| |
| @Override public String toString() { |
| return "(" + symbol + ", " + row + ")"; |
| } |
| } |
| } |