/********************************************************************* Copyright 2014 the Flapi authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ********************************************************************/ package unquietcode.tools.flapi; import unquietcode.tools.flapi.graph.GenericVisitor; import unquietcode.tools.flapi.graph.TransitionVisitor; import unquietcode.tools.flapi.graph.components.AscendingTransition; import unquietcode.tools.flapi.graph.components.StateClass; import unquietcode.tools.flapi.graph.components.TerminalTransition; import unquietcode.tools.flapi.graph.components.Transition; import unquietcode.tools.flapi.runtime.TransitionType; import java.util.IdentityHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** * @author Ben Fagin * @version 08-25-2012 * * Validates a descriptor to ensure that minimum requirements are met. * The "post" validator works on the graph. */ public class DescriptorPostValidator { private final StateClass graph; public DescriptorPostValidator(StateClass graph) { this.graph = graph; } public void validate() { AtomicBoolean value = checkForBlocksWithNoEnd(graph, new IdentityHashMap<StateClass, AtomicBoolean>()); if (!value.get()) { throw reportNoTerminalMethod(graph); } } private AtomicBoolean checkForBlocksWithNoEnd(StateClass state, final Map<StateClass, AtomicBoolean> seen) { if (seen.containsKey(state)) { return seen.get(state); } final AtomicReference<AtomicBoolean> valid = new AtomicReference<AtomicBoolean>(new AtomicBoolean(false)); final AtomicReference<AtomicBoolean> terminal = new AtomicReference<AtomicBoolean>(new AtomicBoolean(false)); final AtomicReference<AtomicBoolean> recursive = new AtomicReference<AtomicBoolean>(new AtomicBoolean(false)); // check for recursion for (Transition transition : state.getTransitions()) { transition.accept(new TransitionVisitor.$() { public @Override void visit(TerminalTransition transition) { handle(transition, true); } public @Override void visit(AscendingTransition transition) { handle(transition, transition.isRequired()); } // check for infinite loops void handle(Transition transition, boolean value) { // for every state in the state chain for (StateClass step : transition.getStateChain()) { // if it is parallel to us, then it's recursive if (step.isLateral(transition.getOwner())) { recursive.get().set(true); return; } } terminal.get().set(terminal.get().get() || value); } }); } // If there was a good method, AND it's recursive, no sweat, // but if there was ONLY a recursive method, then that's an // indication of an infinite loop. if (terminal.get().get() == false && recursive.get().get() == true) { throw new DescriptorBuilderException("Infinite loop detected for block '"+state.getName()+"'."); } valid.set(terminal.get()); seen.put(state, terminal.get()); // check every other transition to ensure that they can find a terminal for (Transition transition : state.getTransitions()) { final TransitionType transitionType = transition.getType(); transition.acceptForTraversal(new GenericVisitor<StateClass>() { public void visit(StateClass next) { AtomicBoolean nextIsTerminal = checkForBlocksWithNoEnd(next, seen); if (transitionType != TransitionType.Recursive) { valid.get().set(valid.get().get() || nextIsTerminal.get()); } } }); } if (valid.get().get() != true) { throw reportNoTerminalMethod(state); } // return terminal because that's what we are really tracking return terminal.get(); } private static DescriptorBuilderException reportNoTerminalMethod(StateClass state) throws DescriptorBuilderException { throw new DescriptorBuilderException("Encountered a block with no terminal method: " + state.getName()); } }