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

software.amazon.event.ruler.ACFinder Maven / Gradle / Ivy

package software.amazon.event.ruler;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static software.amazon.event.ruler.SetOperations.intersection;

/**
 * Matches rules to events as does Finder, but in an array-consistent fashion, thus the AC prefix on the class name.
 */
class ACFinder {

    private static final Patterns ABSENCE_PATTERN = Patterns.absencePatterns();

    private ACFinder() { }

    /**
     * Return any rules that match the fields in the event, but enforce array consistency, i.e. reject
     *  matches where different matches come from the different elements in the same array in the event.
     *
     * @param event the Event structure containing the flattened event information
     * @param machine the compiled state machine
     * @param subRuleContextGenerator the sub-rule context generator
     * @return list of rule names that match. The list may be empty but never null.
     */
    static List matchRules(final Event event, final GenericMachine machine,
                                   final SubRuleContext.Generator subRuleContextGenerator) {
        return find(new ACTask(event, machine), subRuleContextGenerator);
    }

    private static List find(final ACTask task, final SubRuleContext.Generator subRuleContextGenerator) {

        // bootstrap the machine: Start state, first field
        NameState startState = task.startState();
        if (startState == null) {
            return Collections.emptyList();
        }
        moveFrom(null, startState, 0, task, new ArrayMembership(), subRuleContextGenerator);

        // each iteration removes a Step and adds zero or more new ones
        while (task.stepsRemain()) {
            tryStep(task, subRuleContextGenerator);
        }

        return task.getMatchedRules();
    }

    // remove a step from the work queue and see if there's a transition
    private static void tryStep(final ACTask task, final SubRuleContext.Generator subRuleContextGenerator) {
        final ACStep step = task.nextStep();
        final Field field = task.event.fields.get(step.fieldIndex);

        // if we can step from where we are to the new field without violating array consistency
        final ArrayMembership newMembership = ArrayMembership.checkArrayConsistency(step.membershipSoFar, field.arrayMembership);
        if (newMembership != null) {

            // if there are some possible value pattern matches for this key
            final ByteMachine valueMatcher = step.nameState.getTransitionOn(field.name);
            if (valueMatcher != null) {

                // loop through the value pattern matches, if any
                final int nextFieldIndex = step.fieldIndex + 1;
                for (NameStateWithPattern nextNameStateWithPattern : valueMatcher.transitionOn(field.val)) {

                    // we have moved to a new NameState
                    // this NameState might imply a rule match
                    task.collectRules(step.candidateSubRuleIds, nextNameStateWithPattern.getNameState(),
                            nextNameStateWithPattern.getPattern(), subRuleContextGenerator);

                    // set up for attempting to move on from the new state
                    moveFromWithPriorCandidates(step.candidateSubRuleIds, nextNameStateWithPattern.getNameState(),
                            nextNameStateWithPattern.getPattern(), nextFieldIndex, task, newMembership,
                            subRuleContextGenerator);
                }
            }
        }
    }

    private static void tryMustNotExistMatch(final Set candidateSubRuleIds, final NameState nameState,
                                             final ACTask task, int nextKeyIndex, final ArrayMembership arrayMembership,
                                             final SubRuleContext.Generator subRuleContextGenerator) {
        if (!nameState.hasKeyTransitions()) {
            return;
        }

        for (NameState nextNameState : nameState.getNameTransitions(task.event, arrayMembership)) {
            if (nextNameState != null) {
                addNameState(candidateSubRuleIds, nextNameState, ABSENCE_PATTERN, task, nextKeyIndex, arrayMembership,
                        subRuleContextGenerator);
            }
        }
    }

    // Move from a state. Give all the remaining event fields a chance to transition from it.
    private static void moveFrom(final Set candidateSubRuleIdsForNextStep, final NameState nameState,
                                 int fieldIndex, final ACTask task, final ArrayMembership arrayMembership,
                                 final SubRuleContext.Generator subRuleContextGenerator) {
        /*
         * The Name Matchers look for an [ { exists: false } ] match. They
         * will match if a particular key is not present
         * in the event. Hence, if the name state has any matches configured
         * for the [ { exists: false } ] case, we need to evaluate these
         * matches regardless. The fields in the event can be completely
         * disconnected from the fields configured for [ { exists: false } ],
         * and it does not matter if the current field is used in machine.
         *
         * Another possibility is that there can be a final state configured for
         * [ { exists: false } ] match. This state needs to be evaluated for a match
         * even if we have matched all the keys in the event. This is needed because
         * the final state can still be evaluated to true if the particular event
         * does not have the key configured for [ { exists: false } ].
         */
        tryMustNotExistMatch(candidateSubRuleIdsForNextStep, nameState, task, fieldIndex, arrayMembership,
                subRuleContextGenerator);

        while (fieldIndex < task.fieldCount) {
            task.addStep(fieldIndex++, nameState, candidateSubRuleIdsForNextStep, arrayMembership);
        }
    }

    private static void moveFromWithPriorCandidates(final Set candidateSubRuleIds,
                                                    final NameState fromState, final Patterns fromPattern,
                                                    final int fieldIndex, final ACTask task,
                                                    final ArrayMembership arrayMembership,
                                                    final SubRuleContext.Generator subRuleContextGenerator) {
        Set candidateSubRuleIdsForNextStep = calculateCandidateSubRuleIdsForNextStep(candidateSubRuleIds,
                fromState, fromPattern);

        // If there are no more candidate sub-rules, there is no need to proceed further.
        if (candidateSubRuleIdsForNextStep != null && !candidateSubRuleIdsForNextStep.isEmpty()) {
            moveFrom(candidateSubRuleIdsForNextStep, fromState, fieldIndex, task, arrayMembership,
                    subRuleContextGenerator);
        }
    }

    /**
     * Calculate the candidate sub-rule IDs for the next step.
     *
     * @param currentCandidateSubRuleIds The candidate sub-rule IDs for the current step. Use null to indicate that we
     *                                   are on first step and so there are not yet any candidate sub-rules.
     * @param fromState The NameState we are transitioning from.
     * @param fromPattern The pattern we used to transition from fromState.
     * @return The set of candidate sub-rule IDs for the next step. Null means there are no candidates and thus, there
     *         is no point to evaluating subsequent steps.
     */
    private static Set calculateCandidateSubRuleIdsForNextStep(final Set currentCandidateSubRuleIds,
                                                                       final NameState fromState,
                                                                       final Patterns fromPattern) {
        // These are all the sub-rules that use the matched pattern to transition to the next NameState. Note that they
        // are not all candidates as they may have required different values for previously evaluated fields.
        Set subRuleIds = fromState.getNonTerminalSubRuleIdsForPattern(fromPattern);

        // If no sub-rules used the matched pattern to transition to the next NameState, then there are no matches to be
        // found by going further.
        if (subRuleIds == null) {
            return null;
        }

        // If there are no candidate sub-rules, this means we are on the first NameState and must initialize the
        // candidate sub-rules to those that used the matched pattern to transition to the next NameState.
        if (currentCandidateSubRuleIds == null || currentCandidateSubRuleIds.isEmpty()) {
            return subRuleIds;
        }

        // There are candidate sub-rules, so retain only those that used the matched pattern to transition to the next
        // NameState.
        Set candidateSubRuleIdsForNextStep = new HashSet<>();
        intersection(subRuleIds, currentCandidateSubRuleIds, candidateSubRuleIdsForNextStep);
        return candidateSubRuleIdsForNextStep;
    }

    private static void addNameState(Set candidateSubRuleIds, NameState nameState, Patterns pattern,
                                     ACTask task, int nextKeyIndex, final ArrayMembership arrayMembership,
                                     final SubRuleContext.Generator subRuleContextGenerator) {
        // one of the matches might imply a rule match
        task.collectRules(candidateSubRuleIds, nameState, pattern, subRuleContextGenerator);

        moveFromWithPriorCandidates(candidateSubRuleIds, nameState, pattern, nextKeyIndex, task, arrayMembership,
                subRuleContextGenerator);
    }
}