All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.flink.cep.nfa.compiler.NFACompiler Maven / Gradle / Ivy

There is a newer version: 2.0-preview1
Show newest version
/*
 * 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.flink.cep.nfa.compiler;

import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.cep.nfa.NFA;
import org.apache.flink.cep.nfa.State;
import org.apache.flink.cep.nfa.StateTransition;
import org.apache.flink.cep.nfa.StateTransitionAction;
import org.apache.flink.cep.nfa.aftermatch.AfterMatchSkipStrategy;
import org.apache.flink.cep.pattern.GroupPattern;
import org.apache.flink.cep.pattern.MalformedPatternException;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.Quantifier;
import org.apache.flink.cep.pattern.Quantifier.Times;
import org.apache.flink.cep.pattern.WithinType;
import org.apache.flink.cep.pattern.conditions.BooleanConditions;
import org.apache.flink.cep.pattern.conditions.IterativeCondition;
import org.apache.flink.cep.pattern.conditions.RichAndCondition;
import org.apache.flink.cep.pattern.conditions.RichNotCondition;

import java.io.Serializable;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.Stack;

import static org.apache.flink.cep.nfa.compiler.NFAStateNameHandler.STATE_NAME_DELIM;
import static org.apache.flink.util.Preconditions.checkNotNull;

/**
 * Compiler class containing methods to compile a {@link Pattern} into a {@link NFA} or a {@link
 * NFAFactory}.
 */
public class NFACompiler {

    protected static final String ENDING_STATE_NAME = "$endState$";

    /**
     * Compiles the given pattern into a {@link NFAFactory}. The NFA factory can be used to create
     * multiple NFAs.
     *
     * @param pattern Definition of sequence pattern
     * @param timeoutHandling True if the NFA shall return timed out event patterns
     * @param  Type of the input events
     * @return Factory for NFAs corresponding to the given pattern
     */
    @SuppressWarnings("unchecked")
    public static  NFAFactory compileFactory(
            final Pattern pattern, boolean timeoutHandling) {
        if (pattern == null) {
            // return a factory for empty NFAs
            return new NFAFactoryImpl<>(
                    0,
                    Collections.emptyMap(),
                    Collections.>emptyList(),
                    timeoutHandling);
        } else {
            final NFAFactoryCompiler nfaFactoryCompiler = new NFAFactoryCompiler<>(pattern);
            nfaFactoryCompiler.compileFactory();
            return new NFAFactoryImpl<>(
                    nfaFactoryCompiler.getWindowTime(),
                    nfaFactoryCompiler.getWindowTimes(),
                    nfaFactoryCompiler.getStates(),
                    timeoutHandling);
        }
    }

    /**
     * Verifies if the provided pattern can possibly generate empty match. Example of patterns that
     * can possibly generate empty matches are: A*, A?, A* B? etc.
     *
     * @param pattern pattern to check
     * @return true if empty match could potentially match the pattern, false otherwise
     */
    public static boolean canProduceEmptyMatches(final Pattern pattern) {
        NFAFactoryCompiler compiler = new NFAFactoryCompiler<>(checkNotNull(pattern));
        compiler.compileFactory();
        State startState =
                compiler.getStates().stream()
                        .filter(State::isStart)
                        .findFirst()
                        .orElseThrow(
                                () ->
                                        new IllegalStateException(
                                                "Compiler produced no start state. It is a bug. File a jira."));

        Set> visitedStates = new HashSet<>();
        final Stack> statesToCheck = new Stack<>();
        statesToCheck.push(startState);
        while (!statesToCheck.isEmpty()) {
            final State currentState = statesToCheck.pop();
            if (visitedStates.contains(currentState)) {
                continue;
            } else {
                visitedStates.add(currentState);
            }

            for (StateTransition transition : currentState.getStateTransitions()) {
                if (transition.getAction() == StateTransitionAction.PROCEED) {
                    if (transition.getTargetState().isFinal()) {
                        return true;
                    } else {
                        statesToCheck.push(transition.getTargetState());
                    }
                }
            }
        }

        return false;
    }

    /**
     * Converts a {@link Pattern} into graph of {@link State}. It enables sharing of compilation
     * state across methods.
     *
     * @param 
     */
    static class NFAFactoryCompiler {

        private final NFAStateNameHandler stateNameHandler = new NFAStateNameHandler();
        private final Map> stopStates = new HashMap<>();
        private final List> states = new ArrayList<>();
        private final Map windowTimes = new HashMap<>();

        private Optional windowTime;
        private GroupPattern currentGroupPattern;
        private Map, Boolean> firstOfLoopMap = new HashMap<>();
        private Pattern currentPattern;
        private Pattern followingPattern;
        private final AfterMatchSkipStrategy afterMatchSkipStrategy;
        private Map> originalStateMap = new HashMap<>();

        NFAFactoryCompiler(final Pattern pattern) {
            this.currentPattern = pattern;
            afterMatchSkipStrategy = pattern.getAfterMatchSkipStrategy();
            windowTime = Optional.empty();
        }

        /**
         * Compiles the given pattern into a {@link NFAFactory}. The NFA factory can be used to
         * create multiple NFAs.
         */
        void compileFactory() {

            Pattern lastPattern = currentPattern;

            checkPatternNameUniqueness();

            checkPatternSkipStrategy();

            // we're traversing the pattern from the end to the beginning --> the first state is the
            // final state
            State sinkState = createEndingState();
            // add all the normal states
            sinkState = createMiddleStates(sinkState);
            // add the beginning state
            createStartState(sinkState);

            // check the window times between events for pattern
            checkPatternWindowTimes();

            if (lastPattern.getQuantifier().getConsumingStrategy()
                            == Quantifier.ConsumingStrategy.NOT_FOLLOW
                    && (!windowTimes.containsKey(lastPattern.getName())
                            || windowTimes.get(lastPattern.getName()) <= 0)
                    && getWindowTime() == 0) {
                throw new MalformedPatternException(
                        "NotFollowedBy is not supported without windowTime as a last part of a Pattern!");
            }
        }

        AfterMatchSkipStrategy getAfterMatchSkipStrategy() {
            return afterMatchSkipStrategy;
        }

        List> getStates() {
            return states;
        }

        long getWindowTime() {
            return windowTime.orElse(0L);
        }

        Map getWindowTimes() {
            return windowTimes;
        }

        /** Check pattern window times between events. */
        private void checkPatternWindowTimes() {
            windowTime.ifPresent(
                    windowTime -> {
                        if (windowTimes.values().stream().anyMatch(time -> time > windowTime)) {
                            throw new MalformedPatternException(
                                    "The window length between the previous and current event cannot be larger than the window length between the first and last event for a Pattern.");
                        }
                    });
        }

        /** Check pattern after match skip strategy. */
        private void checkPatternSkipStrategy() {
            if (afterMatchSkipStrategy.getPatternName().isPresent()) {
                String patternName = afterMatchSkipStrategy.getPatternName().get();
                Pattern pattern = currentPattern;
                while (pattern.getPrevious() != null && !pattern.getName().equals(patternName)) {
                    pattern = pattern.getPrevious();
                }

                // pattern name match check.
                if (!pattern.getName().equals(patternName)) {
                    throw new MalformedPatternException(
                            "The pattern name specified in AfterMatchSkipStrategy "
                                    + "can not be found in the given Pattern");
                }
            }
        }

        /**
         * Check if there are duplicate pattern names. If yes, it throws a {@link
         * MalformedPatternException}.
         */
        private void checkPatternNameUniqueness() {
            // make sure there is no pattern with name "$endState$"
            stateNameHandler.checkNameUniqueness(ENDING_STATE_NAME);
            Pattern patternToCheck = currentPattern;
            while (patternToCheck != null) {
                checkPatternNameUniqueness(patternToCheck);
                patternToCheck = patternToCheck.getPrevious();
            }
            stateNameHandler.clear();
        }

        /**
         * Check if the given pattern's name is already used or not. If yes, it throws a {@link
         * MalformedPatternException}.
         *
         * @param pattern The pattern to be checked
         */
        private void checkPatternNameUniqueness(final Pattern pattern) {
            if (pattern instanceof GroupPattern) {
                Pattern patternToCheck = ((GroupPattern) pattern).getRawPattern();
                while (patternToCheck != null) {
                    checkPatternNameUniqueness(patternToCheck);
                    patternToCheck = patternToCheck.getPrevious();
                }
            } else {
                stateNameHandler.checkNameUniqueness(pattern.getName());
            }
        }

        /**
         * Retrieves list of conditions resulting in Stop state and names of the corresponding NOT
         * patterns.
         *
         * 

A current not condition can be produced in two cases: * *

    *
  1. the previous pattern is a {@link Quantifier.ConsumingStrategy#NOT_FOLLOW} *
  2. exists a backward path of {@link Quantifier.QuantifierProperty#OPTIONAL} patterns * to {@link Quantifier.ConsumingStrategy#NOT_FOLLOW} *
* *

WARNING: for more info on the second case see: {@link * NFAFactoryCompiler#copyWithoutTransitiveNots(State)} * * @return list of not conditions with corresponding names */ private List, String>> getCurrentNotCondition() { List, String>> notConditions = new ArrayList<>(); Pattern previousPattern = currentPattern; while (previousPattern.getPrevious() != null && (previousPattern .getPrevious() .getQuantifier() .hasProperty(Quantifier.QuantifierProperty.OPTIONAL) || previousPattern.getPrevious().getQuantifier().getConsumingStrategy() == Quantifier.ConsumingStrategy.NOT_FOLLOW)) { previousPattern = previousPattern.getPrevious(); if (previousPattern.getQuantifier().getConsumingStrategy() == Quantifier.ConsumingStrategy.NOT_FOLLOW) { final IterativeCondition notCondition = getTakeCondition(previousPattern); notConditions.add(Tuple2.of(notCondition, previousPattern.getName())); } } return notConditions; } /** * Creates the dummy Final {@link State} of the NFA graph. * * @return dummy Final state */ private State createEndingState() { State endState = createState(ENDING_STATE_NAME, State.StateType.Final); windowTime = currentPattern.getWindowSize().map(Duration::toMillis); return endState; } /** * Creates all the states between Start and Final state. * * @param sinkState the state that last state should point to (always the Final state) * @return the next state after Start in the resulting graph */ private State createMiddleStates(final State sinkState) { State lastSink = sinkState; while (currentPattern.getPrevious() != null) { if (currentPattern.getQuantifier().getConsumingStrategy() == Quantifier.ConsumingStrategy.NOT_FOLLOW) { // skip notFollow patterns, they are converted into edge conditions if ((currentPattern.getWindowSize(WithinType.PREVIOUS_AND_CURRENT).isPresent() || getWindowTime() > 0) && lastSink.isFinal()) { final State notFollow = createState(State.StateType.Pending, true); final IterativeCondition notCondition = getTakeCondition(currentPattern); final State stopState = createStopState(notCondition, currentPattern.getName()); notFollow.addProceed(stopState, notCondition); notFollow.addIgnore(new RichNotCondition<>(notCondition)); lastSink = notFollow; } } else if (currentPattern.getQuantifier().getConsumingStrategy() == Quantifier.ConsumingStrategy.NOT_NEXT) { final State notNext = createState(State.StateType.Normal, true); final IterativeCondition notCondition = getTakeCondition(currentPattern); final State stopState = createStopState(notCondition, currentPattern.getName()); if (lastSink.isFinal()) { // so that the proceed to final is not fired notNext.addIgnore(lastSink, new RichNotCondition<>(notCondition)); } else { notNext.addProceed(lastSink, new RichNotCondition<>(notCondition)); } notNext.addProceed(stopState, notCondition); lastSink = notNext; } else { lastSink = convertPattern(lastSink); } // we traverse the pattern graph backwards followingPattern = currentPattern; currentPattern = currentPattern.getPrevious(); // the window time is the global minimum of all window times of each state currentPattern .getWindowSize() .map(Duration::toMillis) .filter( windowSizeInMillis -> windowSizeInMillis < windowTime.orElse(Long.MAX_VALUE)) .ifPresent( windowSizeInMillis -> windowTime = Optional.of(windowSizeInMillis)); } return lastSink; } /** * Creates the Start {@link State} of the resulting NFA graph. * * @param sinkState the state that Start state should point to (always first state of middle * states) * @return created state */ @SuppressWarnings("unchecked") private State createStartState(State sinkState) { final State beginningState = convertPattern(sinkState); beginningState.makeStart(); return beginningState; } private State convertPattern(final State sinkState) { final State lastSink; final Quantifier quantifier = currentPattern.getQuantifier(); if (quantifier.hasProperty(Quantifier.QuantifierProperty.LOOPING)) { // if loop has started then all notPatterns previous to the optional states are no // longer valid setCurrentGroupPatternFirstOfLoop(false); final State sink = copyWithoutTransitiveNots(sinkState); final State looping = createLooping(sink); setCurrentGroupPatternFirstOfLoop(true); lastSink = createTimesState(looping, sinkState, currentPattern.getTimes()); } else if (quantifier.hasProperty(Quantifier.QuantifierProperty.TIMES)) { lastSink = createTimesState(sinkState, sinkState, currentPattern.getTimes()); } else { lastSink = createSingletonState(sinkState); } addStopStates(lastSink); return lastSink; } private State createState(State.StateType stateType, boolean isTake) { State state = createState(currentPattern.getName(), stateType); if (isTake) { Times times = currentPattern.getTimes(); Optional windowSize = currentPattern.getWindowSize(WithinType.PREVIOUS_AND_CURRENT); if (times == null) { windowSize .map(Duration::toMillis) .ifPresent( windowSizeInMillis -> windowTimes.put(state.getName(), windowSizeInMillis)); } else if (state.getName().contains(STATE_NAME_DELIM)) { times.getWindowSize() .map(Duration::toMillis) .ifPresent( windowSizeInMillis -> windowTimes.put(state.getName(), windowSizeInMillis)); } } return state; } /** * Creates a state with {@link State.StateType#Normal} and adds it to the collection of * created states. Should be used instead of instantiating with new operator. * * @param name the name of the state * @param stateType the type of the state * @return the created state */ private State createState(String name, State.StateType stateType) { String stateName = stateNameHandler.getUniqueInternalName(name); State state = new State<>(stateName, stateType); states.add(state); return state; } private State createStopState( final IterativeCondition notCondition, final String name) { // We should not duplicate the notStates. All states from which we can stop should point // to the same one. State stopState = stopStates.get(name); if (stopState == null) { stopState = createState(name, State.StateType.Stop); stopState.addTake(notCondition); stopStates.put(name, stopState); } return stopState; } /** * This method creates an alternative state that is target for TAKE transition from an * optional State. Accepting an event in optional State discards all not Patterns that were * present before it. * *

E.g for a Pattern * begin("a").notFollowedBy("b").followedByAny("c").optional().followedByAny("d") a sequence * like : {a c b d} is a valid match, but {a b d} is not. * *

NOTICE: This method creates copy only if it necessary. * * @param sinkState a state to create copy without transitive nots * @return the copy of the state itself if no modifications were needed */ private State copyWithoutTransitiveNots(final State sinkState) { final List, String>> currentNotCondition = getCurrentNotCondition(); if (currentNotCondition.isEmpty() || !currentPattern .getQuantifier() .hasProperty(Quantifier.QuantifierProperty.OPTIONAL)) { // we do not create an alternative path if we are NOT in an OPTIONAL state or there // is no NOTs prior to // the optional state return sinkState; } final State copyOfSink = createState(sinkState.getName(), sinkState.getStateType()); for (StateTransition tStateTransition : sinkState.getStateTransitions()) { if (tStateTransition.getAction() == StateTransitionAction.PROCEED) { State targetState = tStateTransition.getTargetState(); boolean remove = false; if (targetState.isStop()) { for (Tuple2, String> notCondition : currentNotCondition) { if (targetState.getName().equals(notCondition.f1)) { remove = true; } } } else { targetState = copyWithoutTransitiveNots(tStateTransition.getTargetState()); } if (!remove) { copyOfSink.addStateTransition( tStateTransition.getAction(), targetState, tStateTransition.getCondition()); } } else { copyOfSink.addStateTransition( tStateTransition.getAction(), tStateTransition .getTargetState() .equals(tStateTransition.getSourceState()) ? copyOfSink : tStateTransition.getTargetState(), tStateTransition.getCondition()); } } return copyOfSink; } private State copy(final State state) { final State copyOfState = createState( NFAStateNameHandler.getOriginalNameFromInternal(state.getName()), state.getStateType()); for (StateTransition tStateTransition : state.getStateTransitions()) { copyOfState.addStateTransition( tStateTransition.getAction(), tStateTransition.getTargetState().equals(tStateTransition.getSourceState()) ? copyOfState : tStateTransition.getTargetState(), tStateTransition.getCondition()); } return copyOfState; } private void addStopStates(final State state) { for (Tuple2, String> notCondition : getCurrentNotCondition()) { final State stopState = createStopState(notCondition.f0, notCondition.f1); state.addProceed(stopState, notCondition.f0); } } private void addStopStateToLooping(final State loopingState) { if (followingPattern != null && followingPattern.getQuantifier().getConsumingStrategy() == Quantifier.ConsumingStrategy.NOT_FOLLOW) { final IterativeCondition notCondition = getTakeCondition(followingPattern); final State stopState = createStopState(notCondition, followingPattern.getName()); loopingState.addProceed(stopState, notCondition); } } /** * Creates a "complex" state consisting of given number of states with same {@link * IterativeCondition}. * * @param sinkState the state that the created state should point to * @param proceedState state that the state being converted should proceed to * @param times number of times the state should be copied * @return the first state of the "complex" state, next state should point to it */ @SuppressWarnings("unchecked") private State createTimesState( final State sinkState, final State proceedState, Times times) { State lastSink = sinkState; setCurrentGroupPatternFirstOfLoop(false); final IterativeCondition untilCondition = (IterativeCondition) currentPattern.getUntilCondition(); final IterativeCondition innerIgnoreCondition = extendWithUntilCondition( getInnerIgnoreCondition(currentPattern), untilCondition, false); final IterativeCondition takeCondition = extendWithUntilCondition( getTakeCondition(currentPattern), untilCondition, true); if (currentPattern.getQuantifier().hasProperty(Quantifier.QuantifierProperty.GREEDY) && times.getFrom() != times.getTo()) { if (untilCondition != null) { State sinkStateCopy = copy(sinkState); originalStateMap.put(sinkState.getName(), sinkStateCopy); } updateWithGreedyCondition(sinkState, takeCondition); } for (int i = times.getFrom(); i < times.getTo(); i++) { lastSink = createSingletonState( lastSink, proceedState, takeCondition, innerIgnoreCondition, true); addStopStateToLooping(lastSink); } for (int i = 0; i < times.getFrom() - 1; i++) { lastSink = createSingletonState( lastSink, null, takeCondition, innerIgnoreCondition, false); addStopStateToLooping(lastSink); } // we created the intermediate states in the loop, now we create the start of the loop. setCurrentGroupPatternFirstOfLoop(true); return createSingletonState( lastSink, proceedState, takeCondition, getIgnoreCondition(currentPattern), isPatternOptional(currentPattern)); } /** * Marks the current group pattern as the head of the TIMES quantifier or not. * * @param isFirstOfLoop whether the current group pattern is the head of the TIMES * quantifier */ @SuppressWarnings("unchecked") private void setCurrentGroupPatternFirstOfLoop(boolean isFirstOfLoop) { if (currentPattern instanceof GroupPattern) { firstOfLoopMap.put((GroupPattern) currentPattern, isFirstOfLoop); } } /** * Checks if the current group pattern is the head of the TIMES/LOOPING quantifier or not a * TIMES/LOOPING quantifier pattern. */ private boolean isCurrentGroupPatternFirstOfLoop() { if (firstOfLoopMap.containsKey(currentGroupPattern)) { return firstOfLoopMap.get(currentGroupPattern); } else { return true; } } /** * Checks if the given pattern is the head pattern of the current group pattern. * * @param pattern the pattern to be checked * @return {@code true} iff the given pattern is in a group pattern and it is the head * pattern of the group pattern, {@code false} otherwise */ private boolean headOfGroup(Pattern pattern) { return currentGroupPattern != null && pattern.getPrevious() == null; } /** * Checks if the given pattern is optional. If the given pattern is the head of a group * pattern, the optional status depends on the group pattern. */ private boolean isPatternOptional(Pattern pattern) { return pattern.getQuantifier().hasProperty(Quantifier.QuantifierProperty.OPTIONAL); } private boolean isHeadOfOptionalGroupPattern(Pattern pattern) { if (!headOfGroup(pattern)) { return false; } return isCurrentGroupPatternFirstOfLoop() && currentGroupPattern .getQuantifier() .hasProperty(Quantifier.QuantifierProperty.OPTIONAL); } /** * Creates a simple single state. For an OPTIONAL state it also consists of a similar state * without the PROCEED edge, so that for each PROCEED transition branches in computation * state graph can be created only once. * * @param sinkState state that the state being converted should point to * @return the created state */ @SuppressWarnings("unchecked") private State createSingletonState(final State sinkState) { return createSingletonState( sinkState, sinkState, getTakeCondition(currentPattern), getIgnoreCondition(currentPattern), isPatternOptional(currentPattern)); } /** * Creates a simple single state. For an OPTIONAL state it also consists of a similar state * without the PROCEED edge, so that for each PROCEED transition branches in computation * state graph can be created only once. * * @param ignoreCondition condition that should be applied to IGNORE transition * @param sinkState state that the state being converted should point to * @param proceedState state that the state being converted should proceed to * @param isOptional whether the state being converted is optional * @return the created state */ @SuppressWarnings("unchecked") private State createSingletonState( final State sinkState, final State proceedState, final IterativeCondition takeCondition, final IterativeCondition ignoreCondition, final boolean isOptional) { if (currentPattern instanceof GroupPattern) { return createGroupPatternState( (GroupPattern) currentPattern, sinkState, proceedState, isOptional); } final State singletonState = createState(State.StateType.Normal, true); // if event is accepted then all notPatterns previous to the optional states are no // longer valid final State sink = copyWithoutTransitiveNots(sinkState); singletonState.addTake(sink, takeCondition); // if no element accepted the previous nots are still valid. final IterativeCondition proceedCondition = getTrueFunction(); if (isOptional) { if (currentPattern .getQuantifier() .hasProperty(Quantifier.QuantifierProperty.GREEDY)) { final IterativeCondition untilCondition = (IterativeCondition) currentPattern.getUntilCondition(); if (untilCondition != null) { singletonState.addProceed( originalStateMap.get(proceedState.getName()), new RichAndCondition<>(proceedCondition, untilCondition)); } singletonState.addProceed( proceedState, untilCondition != null ? new RichAndCondition<>( proceedCondition, new RichNotCondition<>(untilCondition)) : proceedCondition); } else { singletonState.addProceed(proceedState, proceedCondition); } } if (ignoreCondition != null) { final State ignoreState; if (isOptional || isHeadOfOptionalGroupPattern(currentPattern)) { ignoreState = createState(State.StateType.Normal, false); ignoreState.addTake(sink, takeCondition); ignoreState.addIgnore(ignoreCondition); addStopStates(ignoreState); } else { ignoreState = singletonState; } singletonState.addIgnore(ignoreState, ignoreCondition); } return singletonState; } /** * Create all the states for the group pattern. * * @param groupPattern the group pattern to create the states for * @param sinkState the state that the group pattern being converted should point to * @param proceedState the state that the group pattern being converted should proceed to * @param isOptional whether the group pattern being converted is optional * @return the first state of the states of the group pattern */ private State createGroupPatternState( final GroupPattern groupPattern, final State sinkState, final State proceedState, final boolean isOptional) { final IterativeCondition proceedCondition = getTrueFunction(); Pattern oldCurrentPattern = currentPattern; Pattern oldFollowingPattern = followingPattern; GroupPattern oldGroupPattern = currentGroupPattern; State lastSink = sinkState; currentGroupPattern = groupPattern; currentPattern = groupPattern.getRawPattern(); lastSink = createMiddleStates(lastSink); lastSink = convertPattern(lastSink); if (isOptional) { // for the first state of a group pattern, its PROCEED edge should point to // the following state of that group pattern lastSink.addProceed(proceedState, proceedCondition); } currentPattern = oldCurrentPattern; followingPattern = oldFollowingPattern; currentGroupPattern = oldGroupPattern; return lastSink; } /** * Create the states for the group pattern as a looping one. * * @param groupPattern the group pattern to create the states for * @param sinkState the state that the group pattern being converted should point to * @return the first state of the states of the group pattern */ private State createLoopingGroupPatternState( final GroupPattern groupPattern, final State sinkState) { final IterativeCondition proceedCondition = getTrueFunction(); Pattern oldCurrentPattern = currentPattern; Pattern oldFollowingPattern = followingPattern; GroupPattern oldGroupPattern = currentGroupPattern; final State dummyState = createState(State.StateType.Normal, true); State lastSink = dummyState; currentGroupPattern = groupPattern; currentPattern = groupPattern.getRawPattern(); lastSink = createMiddleStates(lastSink); lastSink = convertPattern(lastSink); lastSink.addProceed(sinkState, proceedCondition); dummyState.addProceed(lastSink, proceedCondition); currentPattern = oldCurrentPattern; followingPattern = oldFollowingPattern; currentGroupPattern = oldGroupPattern; return lastSink; } /** * Creates the given state as a looping one. Looping state is one with TAKE edge to itself * and PROCEED edge to the sinkState. It also consists of a similar state without the * PROCEED edge, so that for each PROCEED transition branches in computation state graph can * be created only once. * * @param sinkState the state that the converted state should point to * @return the first state of the created complex state */ @SuppressWarnings("unchecked") private State createLooping(final State sinkState) { if (currentPattern instanceof GroupPattern) { return createLoopingGroupPatternState((GroupPattern) currentPattern, sinkState); } final IterativeCondition untilCondition = (IterativeCondition) currentPattern.getUntilCondition(); final IterativeCondition ignoreCondition = extendWithUntilCondition( getInnerIgnoreCondition(currentPattern), untilCondition, false); final IterativeCondition takeCondition = extendWithUntilCondition( getTakeCondition(currentPattern), untilCondition, true); IterativeCondition proceedCondition = getTrueFunction(); final State loopingState = createState(State.StateType.Normal, true); if (currentPattern.getQuantifier().hasProperty(Quantifier.QuantifierProperty.GREEDY)) { if (untilCondition != null) { State sinkStateCopy = copy(sinkState); loopingState.addProceed( sinkStateCopy, new RichAndCondition<>(proceedCondition, untilCondition)); originalStateMap.put(sinkState.getName(), sinkStateCopy); } loopingState.addProceed( sinkState, untilCondition != null ? new RichAndCondition<>( proceedCondition, new RichNotCondition<>(untilCondition)) : proceedCondition); updateWithGreedyCondition(sinkState, getTakeCondition(currentPattern)); } else { loopingState.addProceed(sinkState, proceedCondition); } loopingState.addTake(takeCondition); addStopStateToLooping(loopingState); if (ignoreCondition != null) { final State ignoreState = createState(State.StateType.Normal, false); ignoreState.addTake(loopingState, takeCondition); ignoreState.addIgnore(ignoreCondition); loopingState.addIgnore(ignoreState, ignoreCondition); addStopStateToLooping(ignoreState); } return loopingState; } /** * This method extends the given condition with stop(until) condition if necessary. The * until condition needs to be applied only if both of the given conditions are not null. * * @param condition the condition to extend * @param untilCondition the until condition to join with the given condition * @param isTakeCondition whether the {@code condition} is for {@code TAKE} edge * @return condition with AND applied or the original condition */ private IterativeCondition extendWithUntilCondition( IterativeCondition condition, IterativeCondition untilCondition, boolean isTakeCondition) { if (untilCondition != null && condition != null) { return new RichAndCondition<>(new RichNotCondition<>(untilCondition), condition); } else if (untilCondition != null && isTakeCondition) { return new RichNotCondition<>(untilCondition); } return condition; } /** * @return The {@link IterativeCondition condition} for the {@code IGNORE} edge that * corresponds to the specified {@link Pattern} and extended with stop(until) condition * if necessary. It is applicable only for inner states of a complex state like looping * or times. */ @SuppressWarnings("unchecked") private IterativeCondition getInnerIgnoreCondition(Pattern pattern) { Quantifier.ConsumingStrategy consumingStrategy = pattern.getQuantifier().getInnerConsumingStrategy(); if (headOfGroup(pattern)) { // for the head pattern of a group pattern, we should consider the // inner consume strategy of the group pattern consumingStrategy = currentGroupPattern.getQuantifier().getInnerConsumingStrategy(); } IterativeCondition innerIgnoreCondition = null; switch (consumingStrategy) { case STRICT: innerIgnoreCondition = null; break; case SKIP_TILL_NEXT: innerIgnoreCondition = new RichNotCondition<>((IterativeCondition) pattern.getCondition()); break; case SKIP_TILL_ANY: innerIgnoreCondition = BooleanConditions.trueFunction(); break; } if (currentGroupPattern != null && currentGroupPattern.getUntilCondition() != null) { innerIgnoreCondition = extendWithUntilCondition( innerIgnoreCondition, (IterativeCondition) currentGroupPattern.getUntilCondition(), false); } return innerIgnoreCondition; } /** * @return The {@link IterativeCondition condition} for the {@code IGNORE} edge that * corresponds to the specified {@link Pattern} and extended with stop(until) condition * if necessary. For more on strategy see {@link Quantifier} */ @SuppressWarnings("unchecked") private IterativeCondition getIgnoreCondition(Pattern pattern) { Quantifier.ConsumingStrategy consumingStrategy = pattern.getQuantifier().getConsumingStrategy(); if (headOfGroup(pattern)) { // for the head pattern of a group pattern, we should consider the inner consume // strategy // of the group pattern if the group pattern is not the head of the TIMES/LOOPING // quantifier; // otherwise, we should consider the consume strategy of the group pattern if (isCurrentGroupPatternFirstOfLoop()) { consumingStrategy = currentGroupPattern.getQuantifier().getConsumingStrategy(); } else { consumingStrategy = currentGroupPattern.getQuantifier().getInnerConsumingStrategy(); } } IterativeCondition ignoreCondition = null; switch (consumingStrategy) { case STRICT: ignoreCondition = null; break; case SKIP_TILL_NEXT: ignoreCondition = new RichNotCondition<>((IterativeCondition) pattern.getCondition()); break; case SKIP_TILL_ANY: ignoreCondition = BooleanConditions.trueFunction(); break; } if (currentGroupPattern != null && currentGroupPattern.getUntilCondition() != null) { ignoreCondition = extendWithUntilCondition( ignoreCondition, (IterativeCondition) currentGroupPattern.getUntilCondition(), false); } return ignoreCondition; } /** * @return the {@link IterativeCondition condition} for the {@code TAKE} edge that * corresponds to the specified {@link Pattern} and extended with stop(until) condition * if necessary. */ @SuppressWarnings("unchecked") private IterativeCondition getTakeCondition(Pattern pattern) { IterativeCondition takeCondition = (IterativeCondition) pattern.getCondition(); if (currentGroupPattern != null && currentGroupPattern.getUntilCondition() != null) { takeCondition = extendWithUntilCondition( takeCondition, (IterativeCondition) currentGroupPattern.getUntilCondition(), true); } return takeCondition; } /** @return An true function extended with stop(until) condition if necessary. */ @SuppressWarnings("unchecked") private IterativeCondition getTrueFunction() { IterativeCondition trueCondition = BooleanConditions.trueFunction(); if (currentGroupPattern != null && currentGroupPattern.getUntilCondition() != null) { trueCondition = extendWithUntilCondition( trueCondition, (IterativeCondition) currentGroupPattern.getUntilCondition(), true); } return trueCondition; } private void updateWithGreedyCondition( State state, IterativeCondition takeCondition) { for (StateTransition stateTransition : state.getStateTransitions()) { stateTransition.setCondition( new RichAndCondition<>( stateTransition.getCondition(), new RichNotCondition<>(takeCondition))); } } } /** * Factory interface for {@link NFA}. * * @param Type of the input events which are processed by the NFA */ public interface NFAFactory extends Serializable { NFA createNFA(); } /** * Implementation of the {@link NFAFactory} interface. * *

The implementation takes the input type serializer, the window time and the set of states * and their transitions to be able to create an NFA from them. * * @param Type of the input events which are processed by the NFA */ private static class NFAFactoryImpl implements NFAFactory { private static final long serialVersionUID = 8939783698296714379L; private final long windowTime; private final Map windowTimes; private final Collection> states; private final boolean timeoutHandling; private NFAFactoryImpl( long windowTime, Map windowTimes, Collection> states, boolean timeoutHandling) { this.windowTime = windowTime; this.windowTimes = windowTimes; this.states = states; this.timeoutHandling = timeoutHandling; } @Override public NFA createNFA() { return new NFA<>(states, windowTimes, windowTime, timeoutHandling); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy