001/** 002 * 003 * Copyright 2018 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.fsm; 018 019import java.io.PrintWriter; 020import java.lang.reflect.Constructor; 021import java.lang.reflect.InvocationTargetException; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.logging.Logger; 032 033import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.DisconnectedStateDescriptor; 034import org.jivesoftware.smack.util.MultiMap; 035 036public class StateDescriptorGraph { 037 038 private static final Logger LOGGER = Logger.getLogger(StateDescriptorGraph.class.getName()); 039 040 private static GraphVertex<StateDescriptor> addNewStateDescriptorGraphVertex( 041 Class<? extends StateDescriptor> stateDescriptorClass, 042 Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes) 043 throws InstantiationException, IllegalAccessException, IllegalArgumentException, 044 InvocationTargetException, NoSuchMethodException, SecurityException { 045 Constructor<? extends StateDescriptor> stateDescriptorConstructor = stateDescriptorClass.getDeclaredConstructor(); 046 stateDescriptorConstructor.setAccessible(true); 047 StateDescriptor stateDescriptor = stateDescriptorConstructor.newInstance(); 048 GraphVertex<StateDescriptor> graphVertexStateDescriptor = new GraphVertex<>(stateDescriptor); 049 050 GraphVertex<StateDescriptor> previous = graphVertexes.put(stateDescriptorClass, graphVertexStateDescriptor); 051 assert previous == null; 052 053 return graphVertexStateDescriptor; 054 } 055 056 private static final class HandleStateDescriptorGraphVertexContext { 057 private final Set<Class<? extends StateDescriptor>> handledStateDescriptors = new HashSet<>(); 058 Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes; 059 MultiMap<Class<? extends StateDescriptor>, Class<? extends StateDescriptor>> inferredForwardEdges; 060 061 private HandleStateDescriptorGraphVertexContext( 062 Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes, 063 MultiMap<Class<? extends StateDescriptor>, Class<? extends StateDescriptor>> inferredForwardEdges) { 064 this.graphVertexes = graphVertexes; 065 this.inferredForwardEdges = inferredForwardEdges; 066 } 067 068 private boolean recurseInto(Class<? extends StateDescriptor> stateDescriptorClass) { 069 boolean wasAdded = handledStateDescriptors.add(stateDescriptorClass); 070 boolean alreadyHandled = !wasAdded; 071 return alreadyHandled; 072 } 073 074 private GraphVertex<StateDescriptor> getOrConstruct(Class<? extends StateDescriptor> stateDescriptorClass) 075 throws InstantiationException, IllegalAccessException, IllegalArgumentException, 076 InvocationTargetException, NoSuchMethodException, SecurityException { 077 GraphVertex<StateDescriptor> graphVertexStateDescriptor = graphVertexes.get(stateDescriptorClass); 078 079 if (graphVertexStateDescriptor == null) { 080 graphVertexStateDescriptor = addNewStateDescriptorGraphVertex(stateDescriptorClass, graphVertexes); 081 082 for (Class<? extends StateDescriptor> inferredSuccessor : inferredForwardEdges.getAll( 083 stateDescriptorClass)) { 084 graphVertexStateDescriptor.getElement().addSuccessor(inferredSuccessor); 085 } 086 } 087 088 return graphVertexStateDescriptor; 089 } 090 } 091 092 private static void handleStateDescriptorGraphVertex(GraphVertex<StateDescriptor> node, 093 HandleStateDescriptorGraphVertexContext context) 094 throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { 095 Class<? extends StateDescriptor> stateDescriptorClass = node.element.getClass(); 096 boolean alreadyHandled = context.recurseInto(stateDescriptorClass); 097 if (alreadyHandled) { 098 return; 099 } 100 101 Set<Class<? extends StateDescriptor>> successorClasses = node.element.getSuccessors(); 102 int numSuccessors = successorClasses.size(); 103 104 Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> successorStateDescriptors = new HashMap<>( 105 numSuccessors); 106 for (Class<? extends StateDescriptor> successorClass : successorClasses) { 107 GraphVertex<StateDescriptor> successorGraphNode = context.getOrConstruct(successorClass); 108 successorStateDescriptors.put(successorClass, successorGraphNode); 109 } 110 111 switch (numSuccessors) { 112 case 0: 113 throw new IllegalStateException("State " + stateDescriptorClass + " has no successor"); 114 case 1: 115 GraphVertex<StateDescriptor> soleSuccessorNode = successorStateDescriptors.values().iterator().next(); 116 node.addOutgoingEdge(soleSuccessorNode); 117 handleStateDescriptorGraphVertex(soleSuccessorNode, context); 118 return; 119 } 120 121 // We hit a state with multiple successors, perform a topological sort on the successors first. 122 // Process the information regarding subordinates and superiors states. 123 // TODO: Find a better name for preference graph. It is the graph where the precedence information of all 124 // successors is stored, which we will topologically sort to find out which successor we should try first. 125 Map<Class<? extends StateDescriptor>, GraphVertex<Class<? extends StateDescriptor>>> preferenceGraph = new HashMap<>(numSuccessors); 126 for (GraphVertex<StateDescriptor> successorStateDescriptorGraphNode : successorStateDescriptors.values()) { 127 StateDescriptor successorStateDescriptor = successorStateDescriptorGraphNode.element; 128 Class<? extends StateDescriptor> successorStateDescriptorClass = successorStateDescriptor.getClass(); 129 for (Class<? extends StateDescriptor> subordinateClass : successorStateDescriptor.getSubordinates()) { 130 if (!successorClasses.contains(subordinateClass)) { 131 // TODO: Make this an exception. 132 LOGGER.warning(successorStateDescriptor + " points to a subordinate '" + subordinateClass + "' which is not part of the successor set"); 133 continue; 134 } 135 136 GraphVertex<Class<? extends StateDescriptor>> superiorClassNode = lookupAndCreateIfRequired( 137 preferenceGraph, successorStateDescriptorClass); 138 GraphVertex<Class<? extends StateDescriptor>> subordinateClassNode = lookupAndCreateIfRequired( 139 preferenceGraph, subordinateClass); 140 141 superiorClassNode.addOutgoingEdge(subordinateClassNode); 142 } 143 for (Class<? extends StateDescriptor> superiorClass : successorStateDescriptor.getSuperiors()) { 144 if (!successorClasses.contains(superiorClass)) { 145 // TODO: Make this an exception. 146 LOGGER.warning(successorStateDescriptor + " points to a superior '" + superiorClass 147 + "' which is not part of the successor set"); 148 continue; 149 } 150 151 GraphVertex<Class<? extends StateDescriptor>> subordinateClassNode = lookupAndCreateIfRequired( 152 preferenceGraph, successorStateDescriptorClass); 153 GraphVertex<Class<? extends StateDescriptor>> superiorClassNode = lookupAndCreateIfRequired( 154 preferenceGraph, superiorClass); 155 156 superiorClassNode.addOutgoingEdge(subordinateClassNode); 157 } 158 } 159 160 // Perform a topological sort which returns the state descriptor classes in their priority. 161 List<GraphVertex<Class<? extends StateDescriptor>>> sortedSuccessors = topologicalSort(preferenceGraph.values()); 162 163 outerloop: for (Class<? extends StateDescriptor> successorStateDescriptor : successorClasses) { 164 for (GraphVertex<Class<? extends StateDescriptor>> sortedSuccessor : sortedSuccessors) { 165 if (sortedSuccessor.getElement() == successorStateDescriptor) { 166 continue outerloop; 167 } 168 } 169 sortedSuccessors.add(new GraphVertex<>(successorStateDescriptor)); 170 } 171 172 for (GraphVertex<Class<? extends StateDescriptor>> successor : sortedSuccessors) { 173 GraphVertex<StateDescriptor> successorVertex = successorStateDescriptors.get(successor.element); 174 assert successorVertex != null; 175 node.outgoingEdges.add(successorVertex); 176 handleStateDescriptorGraphVertex(successorVertex, context); 177 } 178 } 179 180 public static GraphVertex<StateDescriptor> constructStateDescriptorGraph(Set<Class<? extends StateDescriptor>> backwardEdgeStateDescriptors) 181 throws InstantiationException, IllegalAccessException, IllegalArgumentException, 182 InvocationTargetException, NoSuchMethodException, SecurityException { 183 Map<Class<? extends StateDescriptor>, GraphVertex<StateDescriptor>> graphVertexes = new HashMap<>(); 184 185 final Class<? extends StateDescriptor> initialStatedescriptorClass = DisconnectedStateDescriptor.class; 186 GraphVertex<StateDescriptor> initialNode = addNewStateDescriptorGraphVertex(initialStatedescriptorClass, graphVertexes); 187 188 MultiMap<Class<? extends StateDescriptor>, Class<? extends StateDescriptor>> inferredForwardEdges = new MultiMap<>(); 189 for (Class<? extends StateDescriptor> backwardsEdge : backwardEdgeStateDescriptors) { 190 GraphVertex<StateDescriptor> graphVertexStateDescriptor = addNewStateDescriptorGraphVertex(backwardsEdge, graphVertexes); 191 192 for (Class<? extends StateDescriptor> predecessor : graphVertexStateDescriptor.getElement().getPredeccessors()) { 193 inferredForwardEdges.put(predecessor, backwardsEdge); 194 } 195 } 196 // Ensure that the intial node has their successors inferred. 197 for (Class<? extends StateDescriptor> inferredSuccessorOfInitialStateDescriptor : inferredForwardEdges.getAll(initialStatedescriptorClass)) { 198 initialNode.getElement().addSuccessor(inferredSuccessorOfInitialStateDescriptor); 199 } 200 201 HandleStateDescriptorGraphVertexContext context = new HandleStateDescriptorGraphVertexContext(graphVertexes, inferredForwardEdges); 202 handleStateDescriptorGraphVertex(initialNode, context); 203 204 return initialNode; 205 } 206 207 private static GraphVertex<AbstractXmppStateMachineConnection.State> convertToStateGraph(GraphVertex<StateDescriptor> stateDescriptorVertex, 208 AbstractXmppStateMachineConnection connection, Map<StateDescriptor, GraphVertex<AbstractXmppStateMachineConnection.State>> handledStateDescriptors) { 209 StateDescriptor stateDescriptor = stateDescriptorVertex.getElement(); 210 GraphVertex<AbstractXmppStateMachineConnection.State> stateVertex = handledStateDescriptors.get(stateDescriptor); 211 if (stateVertex != null) { 212 return stateVertex; 213 } 214 215 AbstractXmppStateMachineConnection.State state = stateDescriptor.constructState(connection); 216 stateVertex = new GraphVertex<>(state); 217 handledStateDescriptors.put(stateDescriptor, stateVertex); 218 for (GraphVertex<StateDescriptor> successorStateDescriptorVertex : stateDescriptorVertex.getOutgoingEdges()) { 219 GraphVertex<AbstractXmppStateMachineConnection.State> successorStateVertex = convertToStateGraph(successorStateDescriptorVertex, connection, handledStateDescriptors); 220 // It is important that we keep the order of the edges. This should do it. 221 stateVertex.addOutgoingEdge(successorStateVertex); 222 } 223 224 return stateVertex; 225 } 226 227 static GraphVertex<AbstractXmppStateMachineConnection.State> convertToStateGraph(GraphVertex<StateDescriptor> initialStateDescriptor, 228 AbstractXmppStateMachineConnection connection) { 229 Map<StateDescriptor, GraphVertex<AbstractXmppStateMachineConnection.State>> handledStateDescriptors = new HashMap<>(); 230 GraphVertex<AbstractXmppStateMachineConnection.State> initialState = convertToStateGraph(initialStateDescriptor, connection, 231 handledStateDescriptors); 232 return initialState; 233 } 234 235 // TODO: Graph API after here 236 // This API could possibly factored out into an extra package/class, but then we will probably need a builder for 237 // the graph vertex in order to keep it immutable. 238 public static final class GraphVertex<E> { 239 private final E element; 240 private final List<GraphVertex<E>> outgoingEdges = new ArrayList<>(); 241 242 private VertexColor color = VertexColor.white; 243 244 private GraphVertex(E element) { 245 this.element = element; 246 } 247 248 private void addOutgoingEdge(GraphVertex<E> vertex) { 249 if (outgoingEdges.contains(vertex)) { 250 throw new IllegalArgumentException("This " + this + " already has an outgoing edge to " + vertex); 251 } 252 outgoingEdges.add(vertex); 253 } 254 255 public E getElement() { 256 return element; 257 } 258 259 public List<GraphVertex<E>> getOutgoingEdges() { 260 return Collections.unmodifiableList(outgoingEdges); 261 } 262 263 private enum VertexColor { 264 white, 265 grey, 266 black, 267 } 268 269 @Override 270 public String toString() { 271 return toString(true); 272 } 273 274 public String toString(boolean includeOutgoingEdges) { 275 StringBuilder sb = new StringBuilder(); 276 sb.append("GraphVertex " + element + " [color=" + color 277 + ", identityHashCode=" + System.identityHashCode(this) 278 + ", outgoingEdgeCount=" + outgoingEdges.size()); 279 280 if (includeOutgoingEdges) { 281 sb.append(", outgoingEdges={"); 282 283 for (Iterator<GraphVertex<E>> it = outgoingEdges.iterator(); it.hasNext();) { 284 GraphVertex<E> outgoingEdgeVertex = it.next(); 285 sb.append(outgoingEdgeVertex.toString(false)); 286 if (it.hasNext()) { 287 sb.append(", "); 288 } 289 } 290 sb.append('}'); 291 } 292 293 sb.append(']'); 294 return sb.toString(); 295 } 296 } 297 298 private static GraphVertex<Class<? extends StateDescriptor>> lookupAndCreateIfRequired( 299 Map<Class<? extends StateDescriptor>, GraphVertex<Class<? extends StateDescriptor>>> map, 300 Class<? extends StateDescriptor> clazz) { 301 GraphVertex<Class<? extends StateDescriptor>> vertex = map.get(clazz); 302 if (vertex == null) { 303 vertex = new GraphVertex<>(clazz); 304 map.put(clazz, vertex); 305 } 306 return vertex; 307 } 308 309 private static <E> List<GraphVertex<E>> topologicalSort(Collection<GraphVertex<E>> vertexes) { 310 List<GraphVertex<E>> res = new ArrayList<>(); 311 dfs(vertexes, (vertex) -> res.add(0, vertex), null); 312 return res; 313 } 314 315 private static <E> void dfsVisit(GraphVertex<E> vertex, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) { 316 vertex.color = GraphVertex.VertexColor.grey; 317 318 int edgeCount = 0; 319 320 for (GraphVertex<E> successorVertex : vertex.getOutgoingEdges()) { 321 edgeCount++; 322 if (dfsEdgeFound != null) { 323 dfsEdgeFound.onEdgeFound(vertex, successorVertex, edgeCount); 324 } 325 if (successorVertex.color == GraphVertex.VertexColor.white) { 326 dfsVisit(successorVertex, dfsFinishedVertex, dfsEdgeFound); 327 } 328 } 329 330 vertex.color = GraphVertex.VertexColor.black; 331 if (dfsFinishedVertex != null) { 332 dfsFinishedVertex.onVertexFinished(vertex); 333 } 334 } 335 336 private static <E> void dfs(Collection<GraphVertex<E>> vertexes, DfsFinishedVertex<E> dfsFinishedVertex, DfsEdgeFound<E> dfsEdgeFound) { 337 for (GraphVertex<E> vertex : vertexes) { 338 if (vertex.color == GraphVertex.VertexColor.white) { 339 dfsVisit(vertex, dfsFinishedVertex, dfsEdgeFound); 340 } 341 } 342 } 343 344 public static <E> void stateDescriptorGraphToDot(Collection<GraphVertex<StateDescriptor>> vertexes, 345 PrintWriter dotOut, boolean breakStateName) { 346 dotOut.append("digraph {\n"); 347 dfs(vertexes, null, (from, to, edgeId) -> dotOut.append(" \"") 348 .append(from.element.getFullStateName(breakStateName)) 349 .append("\" -> \"") 350 .append(to.element.getFullStateName(breakStateName)) 351 // Note that 'dot' requires *double* quotes to enclose the value. 352 .append("\" [xlabel=\"") 353 .append(Integer.toString(edgeId)) 354 .append("\"];\n")); 355 dotOut.append("}\n"); 356 } 357 358 // TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack minimum Android SDK level is 24 or higher. 359 private interface DfsFinishedVertex<E> { 360 void onVertexFinished(GraphVertex<E> vertex); 361 } 362 363 // TODO: Replace with java.util.function.Consumer<GraphVertex<E>> once Smack minimum Android SDK level is 24 or higher. 364 private interface DfsEdgeFound<E> { 365 void onEdgeFound(GraphVertex<E> from, GraphVertex<E> to, int edgeId); 366 } 367}