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

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

package software.amazon.event.ruler;

import javax.annotation.Nullable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;

/**
 * An implementation of ByteTransition that represents having taken multiple ByteTransitions simultaneously. This is
 * useful for traversing an NFA, i.e. when a single byte could lead you down multiple different transitions.
 */
public final class CompoundByteTransition extends ByteTransition {

    /**
     * The transitions that have all been simultaneously traversed.
     */
    private final Set byteTransitions;

    /**
     * A view of byteTransitions containing just the shortcuts.
     */
    private Set shortcutTransitions;

    /**
     * A view of byteTransitions containing just the ones that can contain matches.
     */
    private Set matchableTransitions;

    /**
     * A ByteTransition representing all nextByteStates from byteTransitions.
     */
    private ByteTransition transitionForNextByteState;

    private CompoundByteTransition(Set byteTransitions) {
        this.byteTransitions = Collections.unmodifiableSet(byteTransitions);

        Set shortcutTransitions = new HashSet<>();
        Set matchableTransitions = new HashSet<>();
        Set nextByteStates = new HashSet<>();

        this.byteTransitions.forEach(t -> {
            if (t.isShortcutTrans()) {
                shortcutTransitions.add((ShortcutTransition) t);
            }
            if (t.isMatchTrans()) {
                matchableTransitions.add(t);
            }
            ByteState nextByteState = t.getNextByteState();
            if (nextByteState != null) {
                nextByteStates.add(nextByteState);
            }
        });

        this.shortcutTransitions = Collections.unmodifiableSet(shortcutTransitions);
        this.matchableTransitions = Collections.unmodifiableSet(matchableTransitions);
        if (nextByteStates.equals(byteTransitions)) {
            this.transitionForNextByteState = this;
        } else {
            this.transitionForNextByteState = coalesce(nextByteStates);
        }
    }

    static  T coalesce(Iterable singles) {
        Iterator iterator = singles.iterator();
        if (!iterator.hasNext()) {
            return null;
        }

        SingleByteTransition firstElement = iterator.next();
        if (!iterator.hasNext()) {
            return (T) firstElement;
        } else if (singles instanceof Set) {
            return (T) new CompoundByteTransition((Set) singles);
        } else {
            // We expect Iterables with more than one element to always be Sets, so this should be dead code, but adding
            // it here for future-proofing.
            Set set = new HashSet();
            singles.forEach(single -> set.add(single));
            return (T) new CompoundByteTransition(set);
        }
    }

    /**
     * Returns the nextByteState from all byteTransitions with a preference to states that have determinate prefixes.
     * These states are re-usable when adding rules to the machine.
     *
     * @return First byte state with a preference to re-usable states.
     */
    @Override
    @Nullable
    ByteState getNextByteState() {
        ByteState firstNonNull = null;
        for (ByteTransition trans : byteTransitions) {
            for (SingleByteTransition single : trans.expand()) {
                ByteState nextByteState = single.getNextByteState();
                if (nextByteState != null) {
                    if (!nextByteState.hasIndeterminatePrefix()) {
                        return nextByteState;
                    }
                    if (firstNonNull == null) {
                        firstNonNull = nextByteState;
                    }
                }
            }
        }
        return firstNonNull;
    }

    @Override
    public SingleByteTransition setNextByteState(ByteState nextState) {
        return nextState;
    }

    /**
     * Get all transitions represented by this compound transition.
     *
     * @return A set of all transitions represented by this transition.
     */
    @Override
    Set expand() {
        return byteTransitions;
    }

    @Override
    ByteTransition getTransitionForNextByteStates() {
        return transitionForNextByteState;
    }

    /**
     * Get the matches given all of the transitions that have been simultaneously traversed. Excludes matches from
     * shortcut transitions as these are not actual matches based on the characters seen so far during the current
     * traversal.
     *
     * @return All matches from all of the simultaneously traversed transitions.
     */
    @Override
    public Set getMatches() {
        Set matches = new HashSet<>();
        for (SingleByteTransition single : matchableTransitions) {
            single.getMatches().forEach(match -> matches.add(match));
        }
        return matches;
    }

    @Override
    public Set getShortcuts() {
        return shortcutTransitions;
    }

    /**
     * Get the next transition for a UTF-8 byte given all of the transitions that have been simultaneously traversed.
     *
     * @param utf8byte The byte to transition on
     * @return Null if there are no transitions, the actual transition if 1, or a CompoundByteTransition if 2+
     */
    @Override
    public ByteTransition getTransition(byte utf8byte) {
        Set singles = new HashSet<>();
        for (SingleByteTransition transition : this.byteTransitions) {
            ByteTransition nextTransition = transition.getTransition(utf8byte);
            if (nextTransition != null) {
                nextTransition.expand().forEach(t -> singles.add(t));
            }
        }
        return coalesce(singles);
    }

    @Override
    public Set getTransitions() {
        // Determine all unique ceilings held in the ByteMaps of all nextByteStates.
        Set allCeilings = new TreeSet<>();
        for (SingleByteTransition transition : byteTransitions) {
            ByteState nextByteState = transition.getNextByteState();
            if (nextByteState != null) {
                allCeilings.addAll(nextByteState.getCeilings());
            }
        }

        // For each unique ceiling, determine all singular transitions accessible for that byte value, then add
        // coalesced version to final result.
        Set result = new HashSet<>();
        for (Integer ceiling : allCeilings) {
            Set singles = new HashSet<>();
            for (SingleByteTransition transition : byteTransitions) {
                ByteTransition nextTransition = transition.getTransition((byte) (ceiling - 1));
                if (nextTransition != null) {
                    nextTransition.expand().forEach(t -> singles.add(t));
                }
            }

            ByteTransition coalesced = coalesce(singles);
            if (coalesced != null) {
                result.add(coalesced);
            }
        }

        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null || o.getClass() != getClass()) {
            return false;
        }
        CompoundByteTransition other = (CompoundByteTransition) o;
        return Objects.equals(byteTransitions, other.byteTransitions);
    }

    @Override
    public int hashCode() {
        return Objects.hash(byteTransitions);
    }

    @Override
    public String toString() {
        return "CBT: " + byteTransitions.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy