Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing bug where sub-rule would not be considered new if its keys and… #140

Merged
merged 2 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<groupId>software.amazon.event.ruler</groupId>
<artifactId>event-ruler</artifactId>
<name>Event Ruler</name>
<version>1.7.0</version>
<version>1.7.1</version>
<description>Event Ruler is a Java library that allows matching Rules to Events. An event is a list of fields,
which may be given as name/value pairs or as a JSON object. A rule associates event field names with lists of
possible values. There are two reasons to use Ruler: 1/ It's fast; the time it takes to match Events doesn't
Expand Down
45 changes: 28 additions & 17 deletions src/main/software/amazon/event/ruler/ACFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,31 +22,33 @@ private ACFinder() { }
*
* @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<Object> matchRules(final Event event, final GenericMachine<?> machine) {
return find(new ACTask(event, machine));
static List<Object> matchRules(final Event event, final GenericMachine<?> machine,
final SubRuleContext.Generator subRuleContextGenerator) {
return find(new ACTask(event, machine), subRuleContextGenerator);
}

private static List<Object> find(final ACTask task) {
private static List<Object> 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());
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);
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) {
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);

Expand All @@ -65,32 +67,36 @@ private static void tryStep(final ACTask task) {
// we have moved to a new NameState
// this NameState might imply a rule match
task.collectRules(step.candidateSubRuleIds, nextNameStateWithPattern.getNameState(),
nextNameStateWithPattern.getPattern());
nextNameStateWithPattern.getPattern(), subRuleContextGenerator);

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

private static void tryMustNotExistMatch(final Set<Double> candidateSubRuleIds, final NameState nameState,
final ACTask task, int nextKeyIndex, final ArrayMembership arrayMembership) {
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);
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<Double> candidateSubRuleIdsForNextStep, final NameState nameState,
int fieldIndex, final ACTask task, final ArrayMembership arrayMembership) {
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
Expand All @@ -106,7 +112,8 @@ private static void moveFrom(final Set<Double> candidateSubRuleIdsForNextStep, f
* 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);
tryMustNotExistMatch(candidateSubRuleIdsForNextStep, nameState, task, fieldIndex, arrayMembership,
subRuleContextGenerator);

while (fieldIndex < task.fieldCount) {
task.addStep(fieldIndex++, nameState, candidateSubRuleIdsForNextStep, arrayMembership);
Expand All @@ -116,13 +123,15 @@ private static void moveFrom(final Set<Double> candidateSubRuleIdsForNextStep, f
private static void moveFromWithPriorCandidates(final Set<Double> candidateSubRuleIds,
final NameState fromState, final Patterns fromPattern,
final int fieldIndex, final ACTask task,
final ArrayMembership arrayMembership) {
final ArrayMembership arrayMembership,
final SubRuleContext.Generator subRuleContextGenerator) {
Set<Double> 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);
moveFrom(candidateSubRuleIdsForNextStep, fromState, fieldIndex, task, arrayMembership,
subRuleContextGenerator);
}
}

Expand Down Expand Up @@ -163,11 +172,13 @@ private static Set<Double> calculateCandidateSubRuleIdsForNextStep(final Set<Dou
}

private static void addNameState(Set<Double> candidateSubRuleIds, NameState nameState, Patterns pattern,
ACTask task, int nextKeyIndex, final ArrayMembership arrayMembership) {
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);
task.collectRules(candidateSubRuleIds, nameState, pattern, subRuleContextGenerator);

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

8 changes: 5 additions & 3 deletions src/main/software/amazon/event/ruler/ACTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ List<Object> getMatchedRules() {
return new ArrayList<>(matchingRules);
}

void collectRules(final Set<Double> candidateSubRuleIds, final NameState nameState, final Patterns pattern) {
void collectRules(final Set<Double> candidateSubRuleIds, final NameState nameState, final Patterns pattern,
final SubRuleContext.Generator subRuleContextGenerator) {
Set<Double> terminalSubRuleIds = nameState.getTerminalSubRuleIdsForPattern(pattern);
if (terminalSubRuleIds == null) {
return;
Expand All @@ -66,10 +67,11 @@ void collectRules(final Set<Double> candidateSubRuleIds, final NameState nameSta
// 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(nameState.getRule(terminalSubRuleId));
matchingRules.add(subRuleContextGenerator.getNameForGeneratedId(terminalSubRuleId));
}
} else {
intersection(candidateSubRuleIds, terminalSubRuleIds, matchingRules, id -> nameState.getRule(id));
intersection(candidateSubRuleIds, terminalSubRuleIds, matchingRules,
id -> subRuleContextGenerator.getNameForGeneratedId(id));
}
}
}
50 changes: 30 additions & 20 deletions src/main/software/amazon/event/ruler/Finder.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,43 +44,48 @@ private Finder() { }
*
* @param event the fields are those from the JSON expression of the event, sorted by key.
* @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<Object> rulesForEvent(final String[] event, final GenericMachine<?> machine) {
return find(new Task(event, machine));
static List<Object> rulesForEvent(final String[] event, final GenericMachine<?> machine,
final SubRuleContext.Generator subRuleContextGenerator) {
return find(new Task(event, machine), subRuleContextGenerator);
}

/**
* Return any rules that match the fields in the event.
*
* @param event the fields are those from the JSON expression of the event, sorted by key.
* @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<Object> rulesForEvent(final List<String> event, final GenericMachine<?> machine) {
return find(new Task(event, machine));
static List<Object> rulesForEvent(final List<String> event, final GenericMachine<?> machine,
final SubRuleContext.Generator subRuleContextGenerator) {
return find(new Task(event, machine), subRuleContextGenerator);
}

private static List<Object> find(final Task task) {
private static List<Object> find(final Task task, final SubRuleContext.Generator subRuleContextGenerator) {

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

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

return task.getMatchedRules();
}

// Move from a state. Give all the remaining tokens a chance to transition from it
private static void moveFrom(final Set<Double> candidateSubRuleIdsForNextStep, final NameState nameState,
final int tokenIndex, final Task task) {
final int tokenIndex, final Task task,
final SubRuleContext.Generator subRuleContextGenerator) {
/*
* The Name Matchers look for an [ { exists: false } ] match. They
* will match if a particular key is not present
Expand All @@ -96,7 +101,7 @@ private static void moveFrom(final Set<Double> candidateSubRuleIdsForNextStep, f
* the final state can still be evaluated to true if the particular event
* does not have the key configured for [ { exists: false } ].
*/
tryNameMatching(candidateSubRuleIdsForNextStep, nameState, task, tokenIndex);
tryNameMatching(candidateSubRuleIdsForNextStep, nameState, task, tokenIndex, subRuleContextGenerator);

// Add more steps using our new set of candidate sub-rules.
for (int i = tokenIndex; i < task.event.length; i += 2) {
Expand All @@ -108,13 +113,14 @@ private static void moveFrom(final Set<Double> candidateSubRuleIdsForNextStep, f

private static void moveFromWithPriorCandidates(final Set<Double> candidateSubRuleIds,
final NameState fromState, final Patterns fromPattern,
final int tokenIndex, final Task task) {
final int tokenIndex, final Task task,
final SubRuleContext.Generator subRuleContextGenerator) {
Set<Double> 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, tokenIndex, task);
moveFrom(candidateSubRuleIdsForNextStep, fromState, tokenIndex, task, subRuleContextGenerator);
}

}
Expand Down Expand Up @@ -156,13 +162,14 @@ private static Set<Double> calculateCandidateSubRuleIdsForNextStep(final Set<Dou
}

// remove a step from the work queue and see if there's a transition
private static void tryStep(final Task task) {
private static void tryStep(final Task task, final SubRuleContext.Generator subRuleContextGenerator) {
final Step step = task.nextStep();

tryValueMatching(task, step);
tryValueMatching(task, step, subRuleContextGenerator);
}

private static void tryValueMatching(final Task task, Step step) {
private static void tryValueMatching(final Task task, final Step step,
final SubRuleContext.Generator subRuleContextGenerator) {
if (step.keyIndex >= task.event.length) {
return;
}
Expand All @@ -181,30 +188,33 @@ private static void tryValueMatching(final Task task, Step step) {
// loop through the value pattern matches
for (NameStateWithPattern nextNameStateWithPattern : valueMatcher.transitionOn(task.event[step.keyIndex + 1])) {
addNameState(step.candidateSubRuleIds, nextNameStateWithPattern.getNameState(),
nextNameStateWithPattern.getPattern(), task, nextKeyIndex);
nextNameStateWithPattern.getPattern(), task, nextKeyIndex, subRuleContextGenerator);
}
}
}

private static void tryNameMatching(final Set<Double> candidateSubRuleIds, final NameState nameState,
final Task task, int keyIndex) {
final Task task, final int keyIndex,
final SubRuleContext.Generator subRuleContextGenerator) {
if (!nameState.hasKeyTransitions()) {
return;
}

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

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

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

Loading