WalkStateGraphContext.java
/**
*
* 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<? extends StateDescriptor> initialStateClass;
private final Class<? extends StateDescriptor> finalStateClass;
private final Class<? extends StateDescriptor> mandatoryIntermediateState;
private final LoginContext loginContext;
private final List<State> walkedStateGraphPath = new ArrayList<>();
/**
* A linked Map of failed States with their reason as value.
*/
final Map<State, StateTransitionResult> 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<State> maybeReturnMandatoryImmediateState(List<GraphVertex<State>> outgoingStateEdges) {
for (GraphVertex<State> outgoingStateVertex : outgoingStateEdges) {
if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == mandatoryIntermediateState) {
mandatoryIntermediateStateHandled = true;
return outgoingStateVertex;
}
}
return null;
}
public List<State> getWalk() {
return CollectionUtil.newListWith(walkedStateGraphPath);
}
public int getWalkLength() {
return walkedStateGraphPath.size();
}
public void appendWalkTo(List<State> 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<State, StateTransitionResult> 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 <code>true</code> if it would loop, <code>false</code> otherwise.
*/
public boolean wouldCauseCycle(GraphVertex<State> successorStateVertex) {
Set<Class<? extends StateDescriptor>> visited = new HashSet<>();
return wouldCycleRecursive(successorStateVertex, visited);
}
private boolean wouldCycleRecursive(GraphVertex<State> stateVertex, Set<Class<? extends StateDescriptor>> visited) {
Class<? extends StateDescriptor> stateVertexClass = stateVertex.getElement().getStateDescriptor().getClass();
if (stateVertexClass == initialStateClass) {
return true;
}
if (finalStateClass == stateVertexClass || visited.contains(stateVertexClass)) {
return false;
}
visited.add(stateVertexClass);
for (GraphVertex<State> successorStateVertex : stateVertex.getOutgoingEdges()) {
boolean cycle = wouldCycleRecursive(successorStateVertex, visited);
if (cycle) {
return true;
}
}
return false;
}
public static Builder builder(Class<? extends StateDescriptor> initialStateClass, Class<? extends StateDescriptor> finalStateClass) {
return new Builder(initialStateClass, finalStateClass);
}
public static final class Builder {
private final Class<? extends StateDescriptor> initialStateClass;
private final Class<? extends StateDescriptor> finalStateClass;
private Class<? extends StateDescriptor> mandatoryIntermediateState;
private LoginContext loginContext;
private Builder(Class<? extends StateDescriptor> initialStateClass, Class<? extends StateDescriptor> finalStateClass) {
this.initialStateClass = Objects.requireNonNull(initialStateClass);
this.finalStateClass = Objects.requireNonNull(finalStateClass);
}
public Builder withMandatoryIntermediateState(Class<? extends StateDescriptor> 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);
}
}
}