in src/main/software/amazon/event/ruler/ByteMachine.java [1440:1581]
private NameState addEndOfMatch(ByteState state,
ByteState prevState,
final InputCharacter[] characters,
final int charIndex,
final Patterns pattern,
final NameState nameStateCandidate) {
final int length = characters.length;
NameState nameState = (nameStateCandidate == null) ? new NameState() : nameStateCandidate;
if (length == 1 && isWildcard(characters[0])) {
// Only character is '*'. Make the start state a match so empty input is matched.
startStateMatch = new ByteMatch(pattern, nameState);
return nameState;
}
ByteTransition trans = getTransition(state, characters[charIndex]);
// If it is shortcut transition, we need do adjustment first.
if (!trans.isEmpty() && trans.isShortcutTrans()) {
ShortcutTransition shortcut = (ShortcutTransition) trans;
ByteMatch match = shortcut.getMatch();
// In add/delete rule path, match must not be null and must not have other match
assert match != null && SHORTCUT_MATCH_TYPES.contains(match.getPattern().type());
// If it is the same pattern, just return.
if (pattern.equals(match.getPattern())) {
return match.getNextNameState();
}
// Have asserted current match pattern must be value patterns
String valueInCurrentPos = ((ValuePatterns) match.getPattern()).pattern();
final InputCharacter[] charactersInCurrentPos = getParser().parse(match.getPattern().type(),
valueInCurrentPos);
// find the position <m> where the common prefix ends.
int m = charIndex;
for (; m < charactersInCurrentPos.length && m < length; m++) {
if (!charactersInCurrentPos[m].equals(characters[m])) {
break;
}
}
// Extend the prefix part in value to byte transitions, to avoid impact on concurrent read we need firstly
// make the new byte chain ready for using and leave the old transition removing to the last step.
// firstNewState will be head of new byte chain and, to avoid impact on concurrent match traffic in read
// path, it need be linked to current state chain after adjustment done.
ByteState firstNewState = null;
ByteState currentState = state;
for (int k = charIndex; k < m; k++) {
// we need keep the current state always pointed to last character.
if (k != charactersInCurrentPos.length -1) {
final ByteState newByteState = new ByteState();
newByteState.setIndeterminatePrefix(currentState.hasIndeterminatePrefix());
if (k != charIndex) {
putTransitionNextState(currentState, charactersInCurrentPos[k], shortcut, newByteState);
} else {
firstNewState = newByteState;
}
currentState = newByteState;
}
}
// If it reached to last character, link the previous read transition in this character, else create
// shortcut transition. Note: at this time, the previous transition can still keep working.
boolean isShortcutNeeded = m < charactersInCurrentPos.length - 1;
int indexToBeChange = isShortcutNeeded ? m : charactersInCurrentPos.length - 1;
putTransitionMatch(currentState, charactersInCurrentPos[indexToBeChange], isShortcutNeeded ?
new ShortcutTransition() : EmptyByteTransition.INSTANCE, match);
removeTransition(currentState, charactersInCurrentPos[indexToBeChange], shortcut);
// At last, we link the new created chain to the byte state path, so no uncompleted change can be felt by
// reading thread. Note: we already confirmed there is only old shortcut transition at charIndex position,
// now we have move it to new position, so we can directly replace previous transition with new transition
// pointed to new byte state chain.
putTransitionNextState(state, characters[charIndex], shortcut, firstNewState);
}
// If there is a exact match transition on tail of path, after adjustment target transitions, we start
// looking at current remaining characters.
// If this is tail transition, go directly analyse the remaining characters, traverse to tail of chain:
boolean isEligibleForShortcut = true;
int j = charIndex;
for (; j < (length - 1); j++) {
// We do not want to re-use an existing state for the second last character in the case of a final-character
// wildcard pattern. In this case, we will have a self-referencing composite match state, which allows zero
// or many character to satisfy the wildcard. The self-reference would lead to unintended matches for the
// existing patterns.
if (j == length - 2 && isWildcard(characters[j + 1])) {
break;
}
trans = getTransition(state, characters[j]);
if (trans.isEmpty()) {
break;
}
ByteState nextByteState = trans.getNextByteState();
if (nextByteState != null) {
// We cannot re-use a state with an indeterminate prefix without creating unintended matches.
if (nextByteState.hasIndeterminatePrefix()) {
// Since there is more path we are unable to traverse, this means we cannot insert shortcut without
// potentially ignoring matches further down path.
isEligibleForShortcut = false;
break;
}
prevState = state;
state = nextByteState;
} else {
// trans has match but no next state, we need prepare a next next state to add trans for either last
// character or shortcut byte.
final ByteState newByteState = new ByteState();
newByteState.setIndeterminatePrefix(state.hasIndeterminatePrefix());
// Stream will not be empty since trans has been verified as non-empty
SingleByteTransition single = trans.expand().iterator().next();
putTransitionNextState(state, characters[j], single, newByteState);
prevState = state;
state = newByteState;
}
}
// look for a chance to put in a shortcut transition.
// However, for the moment, we only do this for a JSON string match i.e beginning with ", not literals
// like true or false or numbers, because if we do this for numbers produced by
// ComparableNumber.generate(), they can be messed up by addRangePattern.
if (SHORTCUT_MATCH_TYPES.contains(pattern.type())) {
// For exactly match, if it is last character already, we just put the real transition with match there.
if (j == length - 1) {
return insertMatch(characters, j, state, nameState, pattern, prevState);
} else if (isEligibleForShortcut) {
// If current character is not last character, create the shortcut transition with the next
ByteMatch byteMatch = new ByteMatch(pattern, nameState);
addTransition(state, characters[j], new ShortcutTransition().setMatch(byteMatch));
addMatchReferences(byteMatch);
return nameState;
}
}
// For other match type, keep the old logic to extend all characters to byte state path and put the match in the
// tail state.
for (; j < (length - 1); j++) {
ByteState nextByteState = findOrMakeNextByteState(state, prevState, characters, j, pattern, nameState);
prevState = state;
state = nextByteState;
}
return insertMatch(characters, length - 1, state, nameState, pattern, prevState);
}