software.amazon.event.ruler.ByteMap Maven / Gradle / Ivy
package software.amazon.event.ruler;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import static software.amazon.event.ruler.CompoundByteTransition.coalesce;
/**
* Maps byte values to ByteTransitions. Designed to perform well given the constraints that most ByteStates will have a
* very small number of transitions, and that for support of wildcards and regexes, we need to efficiently represent the
* condition where wide ranges of byte values (including *all* of them) will transition to a common next ByteState.
*/
class ByteMap {
/*
* Each map entry represents one or more byte values that share a transition. Null means no transition. The key is a
* single integer which is the ceiling value; all byte values below that and greater than or equal to the floor
* value map to the associated transition or null. The floor value for any transition is the ceiling from the
* previous entry, or zero for the zeroth entry in the map.
*/
private volatile NavigableMap map = new TreeMap<>();
ByteMap() {
map.put(256, null);
}
void putTransition(final byte utf8byte, final SingleByteTransition transition) {
updateTransition(utf8byte, transition, Operation.PUT);
}
void putTransitionForAllBytes(final SingleByteTransition transition) {
NavigableMap newMap = new TreeMap<>();
newMap.put(256, transition);
map = newMap;
}
void addTransition(final byte utf8byte, final SingleByteTransition transition) {
updateTransition(utf8byte, transition, Operation.ADD);
}
void addTransitionForAllBytes(final SingleByteTransition transition) {
NavigableMap newMap = new TreeMap<>(map);
for (Map.Entry entry : newMap.entrySet()) {
Set newSingles = new HashSet<>();
ByteTransition storedTransition = entry.getValue();
expand(storedTransition).forEach(single -> newSingles.add(single));
newSingles.add(transition);
entry.setValue(coalesce(newSingles));
}
map = newMap;
}
void removeTransition(final byte utf8byte, final SingleByteTransition transition) {
updateTransition(utf8byte, transition, Operation.REMOVE);
}
void removeTransitionForAllBytes(final SingleByteTransition transition) {
NavigableMap newMap = new TreeMap<>(map);
for (Map.Entry entry : newMap.entrySet()) {
Set newSingles = new HashSet<>();
ByteTransition storedTransition = entry.getValue();
expand(storedTransition).forEach(single -> newSingles.add(single));
newSingles.remove(transition);
entry.setValue(coalesce(newSingles));
}
map = newMap;
}
/**
* Updates one ceiling=>transition mapping and leaves the map in a consistent state. We go through the map and find
* the entry that contains the byte value. If the new byte value is not at the bottom of the entry, we write a new
* entry representing the part of the entry less than the byte value. Then we write a new entry for the byte value.
* Then we merge entries mapping to the same transition.
*/
private void updateTransition(final byte utf8byte, final SingleByteTransition transition, Operation operation) {
final int index = utf8byte & 0xFF;
ByteTransition target = map.higherEntry(index).getValue();
ByteTransition coalesced;
if (operation == Operation.PUT) {
coalesced = coalesce(transition);
} else {
Iterable targetIterable = expand(target);
if (!targetIterable.iterator().hasNext()) {
coalesced = operation == Operation.ADD ? coalesce(transition) : null;
} else {
Set singles = new HashSet<>();
targetIterable.forEach(single -> singles.add(single));
if (operation == Operation.ADD) {
singles.add(transition);
} else {
singles.remove(transition);
}
coalesced = coalesce(singles);
}
}
final boolean atBottom = index == 0 || map.containsKey(index);
if (!atBottom) {
map.put(index, target);
}
map.put(index + 1, coalesced);
// Merge adjacent mappings with the same transition.
mergeAdjacentInMapIfNeeded(map);
}
/**
* Merge adjacent entries with equal transitions in inputMap.
*
* @param inputMap The map on which we merge adjacent entries with equal transitions.
*/
private static void mergeAdjacentInMapIfNeeded(final NavigableMap inputMap) {
Iterator> iterator = inputMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry next1 = iterator.next();
Map.Entry next2 = inputMap.higherEntry(next1.getKey());
if (next2 != null && expand(next1.getValue()).equals(expand(next2.getValue()))) {
iterator.remove();
}
}
}
boolean isEmpty() {
return numberOfTransitions() == 0;
}
int numberOfTransitions() {
return getSingleByteTransitions().size();
}
boolean hasTransition(final ByteTransition transition) {
return getSingleByteTransitions().contains(transition);
}
ByteTransition getTransition(final byte utf8byte) {
return map.higherEntry(utf8byte & 0xFF).getValue();
}
/**
* Get the transition that all bytes lead to. Could be compound if all bytes lead to more than one transition, or
* could be the empty transition if there are no transitions that all bytes lead to.
*
* @return The transition that all bytes lead to.
*/
ByteTransition getTransitionForAllBytes() {
Set candidates = new HashSet<>();
Iterator iterator = map.values().iterator();
ByteTransition firstByteTransition = iterator.next();
if (firstByteTransition == null) {
return ByteMachine.EmptyByteTransition.INSTANCE;
}
firstByteTransition.expand().forEach(single -> candidates.add(single));
while (iterator.hasNext()) {
ByteTransition nextByteTransition = iterator.next();
if (nextByteTransition == null) {
return ByteMachine.EmptyByteTransition.INSTANCE;
}
Iterable singles = nextByteTransition.expand();
if (singles instanceof Set) {
candidates.retainAll((Set) singles);
} else if (singles instanceof SingleByteTransition) {
SingleByteTransition single = (SingleByteTransition) singles;
if (candidates.contains(single)) {
if (candidates.size() > 1) {
candidates.clear();
candidates.add(single);
}
} else {
if (!candidates.isEmpty()) {
candidates.clear();
}
}
} else {
// singles should always be a Set or SingleByteTransition. Thus, this "else" is expected to be dead code
// but it is here for logical correctness if anything changes in the future.
Set set = new HashSet<>();
singles.forEach(single -> set.add(single));
candidates.retainAll(set);
}
if (candidates.isEmpty()) {
return ByteMachine.EmptyByteTransition.INSTANCE;
}
}
return coalesce(candidates);
}
/**
* Get all transitions contained in this map, whether they are single or compound transitions.
*
* @return All transitions contained in this map.
*/
Set getTransitions() {
Set result = new HashSet<>(map.values().size());
for (ByteTransition transition : map.values()) {
if (transition != null) {
result.add(transition);
}
}
return result;
}
/**
* Get the ceiling values contained in this map.
*
* @return Ceiling values.
*/
Set getCeilings() {
return map.keySet();
}
/**
* Get all transitions contained in this map, with the compound transitions expanded to singular form.
*
* @return All transitions contained in this map, with the compound transitions expanded to singular form.
*/
private Set getSingleByteTransitions() {
Set allTransitions = new HashSet<>();
for (ByteTransition transition : map.values()) {
if (transition != null) {
expand(transition).forEach(single -> allTransitions.add(single));
}
}
return allTransitions;
}
private static Iterable expand(ByteTransition transition) {
if (transition == null) {
return Collections.EMPTY_SET;
}
return transition.expand();
}
// for testing
NavigableMap getMap() {
return map;
}
@Override
public String toString() {
final NavigableMap thisMap = map;
StringBuilder sb = new StringBuilder();
int floor = 0;
for (Map.Entry entry : thisMap.entrySet()) {
int ceiling = entry.getKey();
ByteTransition transition = entry.getValue();
for (int j = (char) floor; j < ceiling; j++) {
StringBuilder targets = new StringBuilder();
for (ByteTransition trans : expand(transition)) {
String target = trans.getClass().getName();
target = target.substring(target.lastIndexOf('.') + 1) + "/" + trans.hashCode();
targets.append(target + ",");
}
if (targets.length() > 0) {
targets.deleteCharAt(targets.length() - 1);
sb.append((char) j).append("->").append(targets).append(" // ");
}
}
floor = ceiling;
}
return sb.toString();
}
private enum Operation {
ADD,
PUT,
REMOVE
}
}