int evaluate()

in src/main/software/amazon/event/ruler/MachineComplexityEvaluator.java [49:117]


    int evaluate(ByteState state) {
        // Upfront cost: generate the map of all matches accessible from every state in the machine.
        Map<SingleByteTransition, Set<ByteMatch>> matchesAccessibleFromEachTransition =
                getMatchesAccessibleFromEachTransition(state);

        Set<ByteTransition> visited = new HashSet<>();
        visited.add(state);
        int maxSize = 0;

        // We'll do a breadth-first-search but it shouldn't matter.
        Queue<ByteTransition> transitions = new LinkedList<>();
        state.getTransitions().forEach(trans -> transitions.add(trans));
        while (!transitions.isEmpty()) {
            ByteTransition transition = transitions.remove();
            if (visited.contains(transition)) {
                continue;
            }
            visited.add(transition);

            // The sum of all the wildcard patterns accessible from each SingleByteTransition we are present in on our
            // current traversal is the number of wildcard rule prefixes matching a theoretical worst-case input value.
            int size = 0;
            for (SingleByteTransition single : transition.expand()) {
                size += getPatternsWithComplexity(matchesAccessibleFromEachTransition.get(single)).size();

                // Look for "transitions for all bytes" (i.e. wildcard transitions). Since an input value that matches
                // foo will also match foo*, we also need to include in our size wildcard patterns accessible from foo*.
                ByteState nextState = single.getNextByteState();
                if (nextState != null) {
                    for (SingleByteTransition transitionForAllBytes : nextState.getTransitionForAllBytes().expand()) {
                        if (!(transitionForAllBytes instanceof ByteMachine.EmptyByteTransition) &&
                                !contains(transition.expand(), transitionForAllBytes)) {
                            size += getPatternsWithComplexity(matchesAccessibleFromEachTransition.get(transitionForAllBytes))
                                    .size();
                        }
                    }
                }
            }
            if (size >= maxComplexity) {
                return maxComplexity;
            }
            if (size > maxSize) {
                maxSize = size;
            }

            // Load up our queue with the next round of transitions, where each transition represents a set of states
            // that could be accessed with a particular byte value.
            ByteTransition nextTransition = transition.getTransitionForNextByteStates();
            if (nextTransition != null) {
                nextTransition.getTransitions().forEach(trans -> transitions.add(trans));
            }
        }

        // Now that we have a maxSize for this ByteMachine, let's recursively get the maxSize for each next NameState
        // accessible via any of this ByteMachine's matches. We will return the maximum maxSize.
        int maxSizeFromNextNameStates = 0;
        Set<ByteMatch> uniqueMatches = new HashSet<>();
        for (Set<ByteMatch> matches : matchesAccessibleFromEachTransition.values()) {
            uniqueMatches.addAll(matches);
        }
        for (ByteMatch match : uniqueMatches) {
            NameState nextNameState = match.getNextNameState();
            if (nextNameState != null) {
                maxSizeFromNextNameStates = Math.max(maxSizeFromNextNameStates, nextNameState.evaluateComplexity(this));
            }
        }

        return Math.max(maxSize, maxSizeFromNextNameStates);
    }