001/** 002 * 003 * Copyright 2018-2020 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.c2s.internal; 018 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.LinkedHashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Set; 026 027import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection.AuthenticatedAndResourceBoundStateDescriptor; 028import org.jivesoftware.smack.fsm.LoginContext; 029import org.jivesoftware.smack.fsm.State; 030import org.jivesoftware.smack.fsm.StateDescriptor; 031import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex; 032import org.jivesoftware.smack.fsm.StateTransitionResult; 033import org.jivesoftware.smack.util.CollectionUtil; 034import org.jivesoftware.smack.util.Objects; 035 036import org.jxmpp.jid.parts.Resourcepart; 037 038public final class WalkStateGraphContext { 039 private final Class<? extends StateDescriptor> initialStateClass; 040 private final Class<? extends StateDescriptor> finalStateClass; 041 private final Class<? extends StateDescriptor> mandatoryIntermediateState; 042 private final LoginContext loginContext; 043 044 private final List<State> walkedStateGraphPath = new ArrayList<>(); 045 046 /** 047 * A linked Map of failed States with their reason as value. 048 */ 049 final Map<State, StateTransitionResult> failedStates = new LinkedHashMap<>(); 050 051 boolean mandatoryIntermediateStateHandled; 052 053 WalkStateGraphContext(Builder builder) { 054 initialStateClass = builder.initialStateClass; 055 finalStateClass = builder.finalStateClass; 056 mandatoryIntermediateState = builder.mandatoryIntermediateState; 057 loginContext = builder.loginContext; 058 } 059 060 public void recordWalkTo(State state) { 061 walkedStateGraphPath.add(state); 062 } 063 064 public boolean isWalksFinalState(StateDescriptor stateDescriptor) { 065 return stateDescriptor.getClass() == finalStateClass; 066 } 067 068 public boolean isFinalStateAuthenticatedAndResourceBound() { 069 return finalStateClass == AuthenticatedAndResourceBoundStateDescriptor.class; 070 } 071 072 public GraphVertex<State> maybeReturnMandatoryImmediateState(List<GraphVertex<State>> outgoingStateEdges) { 073 for (GraphVertex<State> outgoingStateVertex : outgoingStateEdges) { 074 if (outgoingStateVertex.getElement().getStateDescriptor().getClass() == mandatoryIntermediateState) { 075 mandatoryIntermediateStateHandled = true; 076 return outgoingStateVertex; 077 } 078 } 079 080 return null; 081 } 082 083 public List<State> getWalk() { 084 return CollectionUtil.newListWith(walkedStateGraphPath); 085 } 086 087 public int getWalkLength() { 088 return walkedStateGraphPath.size(); 089 } 090 091 public void appendWalkTo(List<State> walk) { 092 walk.addAll(walkedStateGraphPath); 093 } 094 095 public LoginContext getLoginContext() { 096 return loginContext; 097 } 098 099 public boolean stateAlreadyVisited(State state) { 100 return walkedStateGraphPath.contains(state); 101 } 102 103 public void recordFailedState(State state, StateTransitionResult stateTransitionResult) { 104 failedStates.put(state, stateTransitionResult); 105 } 106 107 public Map<State, StateTransitionResult> getFailedStates() { 108 return new HashMap<>(failedStates); 109 } 110 111 /** 112 * Check if the way to the final state via the given successor state that would loop, i.e., lead over the initial state and 113 * thus from a cycle. 114 * 115 * @param successorStateVertex the successor state to use on the way. 116 * @return <code>true</code> if it would loop, <code>false</code> otherwise. 117 */ 118 public boolean wouldCauseCycle(GraphVertex<State> successorStateVertex) { 119 Set<Class<? extends StateDescriptor>> visited = new HashSet<>(); 120 return wouldCycleRecursive(successorStateVertex, visited); 121 } 122 123 private boolean wouldCycleRecursive(GraphVertex<State> stateVertex, Set<Class<? extends StateDescriptor>> visited) { 124 Class<? extends StateDescriptor> stateVertexClass = stateVertex.getElement().getStateDescriptor().getClass(); 125 126 if (stateVertexClass == initialStateClass) { 127 return true; 128 } 129 if (finalStateClass == stateVertexClass || visited.contains(stateVertexClass)) { 130 return false; 131 } 132 133 visited.add(stateVertexClass); 134 135 for (GraphVertex<State> successorStateVertex : stateVertex.getOutgoingEdges()) { 136 boolean cycle = wouldCycleRecursive(successorStateVertex, visited); 137 if (cycle) { 138 return true; 139 } 140 } 141 142 return false; 143 } 144 145 public static Builder builder(Class<? extends StateDescriptor> initialStateClass, Class<? extends StateDescriptor> finalStateClass) { 146 return new Builder(initialStateClass, finalStateClass); 147 } 148 149 public static final class Builder { 150 private final Class<? extends StateDescriptor> initialStateClass; 151 private final Class<? extends StateDescriptor> finalStateClass; 152 private Class<? extends StateDescriptor> mandatoryIntermediateState; 153 private LoginContext loginContext; 154 155 private Builder(Class<? extends StateDescriptor> initialStateClass, Class<? extends StateDescriptor> finalStateClass) { 156 this.initialStateClass = Objects.requireNonNull(initialStateClass); 157 this.finalStateClass = Objects.requireNonNull(finalStateClass); 158 } 159 160 public Builder withMandatoryIntermediateState(Class<? extends StateDescriptor> mandatoryIntermedidateState) { 161 this.mandatoryIntermediateState = mandatoryIntermedidateState; 162 return this; 163 } 164 165 public Builder withLoginContext(String username, String password, Resourcepart resource) { 166 LoginContext loginContext = new LoginContext(username, password, resource); 167 return withLoginContext(loginContext); 168 } 169 170 public Builder withLoginContext(LoginContext loginContext) { 171 this.loginContext = loginContext; 172 return this; 173 } 174 175 public WalkStateGraphContext build() { 176 return new WalkStateGraphContext(this); 177 } 178 } 179}