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

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
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy