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}