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

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

package software.amazon.event.ruler;

import javax.annotation.concurrent.ThreadSafe;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;

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

/**
 * Represents the state of a rule-finding project.
 */
@ThreadSafe
class Task {

    // What we're trying to match rules to
    public final String[] event;

    // the rules that matched the event, if we find any
    private final Set matchingRules = new HashSet<>();

    // Steps queued up for processing
    private final Queue stepQueue = new ArrayDeque<>();

    // Visiting the same Step multiple times will give the same outcome for all visits,
    // as we are not mutating the state of Task nor Step nor GenericMachine as part of the visit.
    // To prevent explosion of complexity we need to prevent queueing up steps that are still
    // waiting in the stepQueue or we have already ran. We end up holding on to some amount
    // of memory during Task evaluation to track this, but it has an upper bound that is O(n * m) where
    // n = number of values in input event
    // m = number of nodes in the compiled state machine
    private final Set seenSteps = new HashSet<>();

    // the state machine
    private final GenericMachine machine;

    Task(final List event, final GenericMachine machine) {
        this(event.toArray(new String[0]), machine);
    }

    Task(final String[] event, final GenericMachine machine) {
        this.event = event;
        this.machine = machine;
    }

    NameState startState() {
        return machine.getStartState();
    }

    // The field used means all steps in the field must be all used individually.
    boolean isFieldUsed(final String field) {
        if (field.contains(".")) {
            String[] steps = field.split("\\.");
            return Arrays.stream(steps).allMatch(machine::isFieldStepUsed);
        }
        return machine.isFieldStepUsed(field);
    }

    Step nextStep() {
        return stepQueue.remove();
    }

    void addStep(final Step step) {
        // queue it up only if it's the first time we're trying to queue it up
        // otherwise bad things happen, see comment on seenSteps collection
        if (seenSteps.add(step)) {
            stepQueue.add(step);
        }
    }

    boolean stepsRemain() {
        return !stepQueue.isEmpty();
    }

    List getMatchedRules() {
        return new ArrayList<>(matchingRules);
    }

    void collectRules(final Set candidateSubRuleIds, final NameState nameState, final Patterns pattern,
                      final SubRuleContext.Generator subRuleContextGenerator) {
        Set terminalSubRuleIds = nameState.getTerminalSubRuleIdsForPattern(pattern);
        if (terminalSubRuleIds == null) {
            return;
        }

        // If no candidates, that means we're on the first step, so all sub-rules are candidates.
        if (candidateSubRuleIds == null || candidateSubRuleIds.isEmpty()) {
            for (Double terminalSubRuleId : terminalSubRuleIds) {
                matchingRules.add(subRuleContextGenerator.getNameForGeneratedId(terminalSubRuleId));
            }
        } else {
            intersection(candidateSubRuleIds, terminalSubRuleIds, matchingRules,
                    id -> subRuleContextGenerator.getNameForGeneratedId(id));
        }
    }
}