/** * * Copyright 2018-2020 Florian Schmaus * * 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 org.jivesoftware.smack.c2s.internal; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor; import org.jivesoftware.smack.fsm.LoginContext; import org.jivesoftware.smack.fsm.State; import org.jivesoftware.smack.fsm.StateDescriptor; import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex; import org.jivesoftware.smack.fsm.StateTransitionResult; import org.jivesoftware.smack.util.CollectionUtil; import org.jivesoftware.smack.util.Objects; import org.jxmpp.jid.parts.Resourcepart; public final class WalkStateGraphContext { private final Class initialStateClass; private final Class finalStateClass; private final Class mandatoryIntermediateState; private final LoginContext loginContext; private final List walkedStateGraphPath = new ArrayList<>(); /** * A linked Map of failed States with their reason as value. */ final Map failedStates = new LinkedHashMap<>(); boolean mandatoryIntermediateStateHandled; WalkStateGraphContext(Builder builder) { initialStateClass = builder.initialStateClass; finalStateClass = builder.finalStateClass; mandatoryIntermediateState = builder.mandatoryIntermediateState; loginContext = builder.loginContext; } public void recordWalkTo(State state) { walkedStateGraphPath.add(state); } public boolean isWalksFinalState(StateDescriptor stateDescriptor) { return stateDescriptor.getClass() == finalStateClass; } public boolean isFinalStateAuthenticatedAndResourceBound() { return finalStateClass == AuthenticatedAndResourceBoundStateDescriptor.class; } public GraphVertex maybeReturnMandatoryImmediateState(List> outgoingStateEdges) { for (GraphVertex outgoingStateVertex : outgoingStateEdges) { if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == mandatoryIntermediateState) { mandatoryIntermediateStateHandled = true; return outgoingStateVertex; } } return null; } public List getWalk() { return CollectionUtil.newListWith(walkedStateGraphPath); } public int getWalkLength() { return walkedStateGraphPath.size(); } public void appendWalkTo(List walk) { walk.addAll(walkedStateGraphPath); } public LoginContext getLoginContext() { return loginContext; } public boolean stateAlreadyVisited(State state) { return walkedStateGraphPath.contains(state); } public void recordFailedState(State state, StateTransitionResult stateTransitionResult) { failedStates.put(state, stateTransitionResult); } public Map getFailedStates() { return new HashMap<>(failedStates); } /** * Check if the way to the final state via the given successor state that would loop, i.e., lead over the initial state and * thus from a cycle. * * @param successorStateVertex the successor state to use on the way. * @return true if it would loop, false otherwise. */ public boolean wouldCauseCycle(GraphVertex successorStateVertex) { Set> visited = new HashSet<>(); return wouldCycleRecursive(successorStateVertex, visited); } private boolean wouldCycleRecursive(GraphVertex stateVertex, Set> visited) { Class stateVertexClass = stateVertex.getElement().getStateDescriptor().getClass(); if (stateVertexClass == initialStateClass) { return true; } if (finalStateClass == stateVertexClass || visited.contains(stateVertexClass)) { return false; } visited.add(stateVertexClass); for (GraphVertex successorStateVertex : stateVertex.getOutgoingEdges()) { boolean cycle = wouldCycleRecursive(successorStateVertex, visited); if (cycle) { return true; } } return false; } public static Builder builder(Class initialStateClass, Class finalStateClass) { return new Builder(initialStateClass, finalStateClass); } public static final class Builder { private final Class initialStateClass; private final Class finalStateClass; private Class mandatoryIntermediateState; private LoginContext loginContext; private Builder(Class initialStateClass, Class finalStateClass) { this.initialStateClass = Objects.requireNonNull(initialStateClass); this.finalStateClass = Objects.requireNonNull(finalStateClass); } public Builder withMandatoryIntermediateState(Class mandatoryIntermedidateState) { this.mandatoryIntermediateState = mandatoryIntermedidateState; return this; } public Builder withLoginContext(String username, String password, Resourcepart resource) { LoginContext loginContext = new LoginContext(username, password, resource); return withLoginContext(loginContext); } public Builder withLoginContext(LoginContext loginContext) { this.loginContext = loginContext; return this; } public WalkStateGraphContext build() { return new WalkStateGraphContext(this); } } }