/* * $Id: mxGraphHierarchyModel.java,v 1.12 2010-10-19 13:49:16 david Exp $ * Copyright (c) 2005-2010, David Benson, Gaudenz Alder */ package com.mxgraph.layout.hierarchical.model; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Hashtable; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import com.mxgraph.layout.hierarchical.mxHierarchicalLayout; import com.mxgraph.view.mxGraph; /** * Internal model of a hierarchical graph. This model stores nodes and edges * equivalent to the real graph nodes and edges, but also stores the rank of the * cells, the order within the ranks and the new candidate locations of cells. * The internal model also reverses edge direction were appropriate , ignores * self-loop and groups parallels together under one edge object. */ public class mxGraphHierarchyModel { /** * Whether the rank assignment is done from the sinks or sources. */ protected boolean scanRanksFromSinks = true; /** * Stores the largest rank number allocated */ public int maxRank; /** * Map from graph vertices to internal model nodes */ protected Map<Object, mxGraphHierarchyNode> vertexMapper = null; /** * Map from graph edges to internal model edges */ protected Map<Object, mxGraphHierarchyEdge> edgeMapper = null; /** * Mapping from rank number to actual rank */ public Map<Integer, mxGraphHierarchyRank> ranks = null; /** * Store of roots of this hierarchy model, these are real graph cells, not * internal cells */ public List<Object> roots; /** * The parent cell whose children are being laid out */ public Object parent = null; /** * Count of the number of times the ancestor dfs has been used */ protected int dfsCount = 0; /** * Whether or not cells are ordered according to the order in the graph * model. Defaults to false since sorting usually produces quadratic * performance. Note that since JGraph 6 returns edges in a deterministic * order, it might be that this layout is always deterministic using that * JGraph regardless of this flag setting (i.e. leave it false in that case) */ protected boolean deterministic = false; /** High value to start source layering scan rank value from */ private final int SOURCESCANSTARTRANK = 100000000; /** * Creates an internal ordered graph model using the vertices passed in. If * there are any, leftward edge need to be inverted in the internal model * * @param layout * the enclosing layout object * @param vertices * the vertices for this hierarchy * @param ordered * whether or not the vertices are already ordered * @param deterministic * whether or not this layout should be deterministic on each * @param scanRanksFromSinks * Whether the rank assignment is done from the sinks or sources. * usage */ public mxGraphHierarchyModel(mxHierarchicalLayout layout, Object[] vertices, List<Object> roots, Object parent, boolean ordered, boolean deterministic, boolean scanRanksFromSinks) { mxGraph graph = layout.getGraph(); this.deterministic = deterministic; this.scanRanksFromSinks = scanRanksFromSinks; this.roots = roots; this.parent = parent; if (vertices == null) { vertices = graph.getChildVertices(parent); } if (ordered) { formOrderedHierarchy(layout, vertices, parent); } else { // map of cells to internal cell needed for second run through // to setup the sink of edges correctly. Guess size by number // of edges is roughly same as number of vertices. vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>( vertices.length); edgeMapper = new Hashtable<Object, mxGraphHierarchyEdge>( vertices.length); if (scanRanksFromSinks) { maxRank = 0; } else { maxRank = SOURCESCANSTARTRANK; } mxGraphHierarchyNode[] internalVertices = new mxGraphHierarchyNode[vertices.length]; createInternalCells(layout, vertices, internalVertices); // Go through edges set their sink values. Also check the // ordering if and invert edges if necessary for (int i = 0; i < vertices.length; i++) { Collection<mxGraphHierarchyEdge> edges = internalVertices[i].connectsAsSource; Iterator<mxGraphHierarchyEdge> iter = edges.iterator(); while (iter.hasNext()) { mxGraphHierarchyEdge internalEdge = iter.next(); Collection<Object> realEdges = internalEdge.edges; Iterator<Object> iter2 = realEdges.iterator(); if (iter2.hasNext()) { Object realEdge = iter2.next(); Object targetCell = graph.getView().getVisibleTerminal( realEdge, false); mxGraphHierarchyNode internalTargetCell = vertexMapper .get(targetCell); if (internalTargetCell != null && internalVertices[i] != internalTargetCell) { internalEdge.target = internalTargetCell; if (internalTargetCell.connectsAsTarget.size() == 0) { internalTargetCell.connectsAsTarget = new LinkedHashSet<mxGraphHierarchyEdge>( 4); } internalTargetCell.connectsAsTarget .add(internalEdge); } } } // Use the temp variable in the internal nodes to mark this // internal vertex as having been visited. internalVertices[i].temp[0] = 1; } } } /** * Creates an internal ordered graph model using the vertices passed in. If * there are any, leftward edge need to be inverted in the internal model * * @param layout * reference to the layout algorithm * @param vertices * the vertices to be laid out */ public void formOrderedHierarchy(mxHierarchicalLayout layout, Object[] vertices, Object parent) { mxGraph graph = layout.getGraph(); // map of cells to internal cell needed for second run through // to setup the sink of edges correctly. Guess size by number // of edges is roughly same as number of vertices. vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>( vertices.length * 2); edgeMapper = new Hashtable<Object, mxGraphHierarchyEdge>( vertices.length); maxRank = 0; mxGraphHierarchyNode[] internalVertices = new mxGraphHierarchyNode[vertices.length]; createInternalCells(layout, vertices, internalVertices); // Go through edges set their sink values. Also check the // ordering if and invert edges if necessary // Need a temporary list to store which of these edges have been // inverted in the internal model. If connectsAsSource were changed // in the following while loop we'd get a // ConcurrentModificationException List<mxGraphHierarchyEdge> tempList = new ArrayList<mxGraphHierarchyEdge>(); for (int i = 0; i < vertices.length; i++) { Collection<mxGraphHierarchyEdge> edges = internalVertices[i].connectsAsSource; Iterator<mxGraphHierarchyEdge> iter = edges.iterator(); while (iter.hasNext()) { mxGraphHierarchyEdge internalEdge = iter.next(); Collection<Object> realEdges = internalEdge.edges; Iterator<Object> iter2 = realEdges.iterator(); if (iter2.hasNext()) { Object realEdge = iter2.next(); Object targetCell = graph.getView().getVisibleTerminal( realEdge, false); mxGraphHierarchyNode internalTargetCell = vertexMapper .get(targetCell); if (internalTargetCell != null && internalVertices[i] != internalTargetCell) { internalEdge.target = internalTargetCell; if (internalTargetCell.connectsAsTarget.size() == 0) { internalTargetCell.connectsAsTarget = new ArrayList<mxGraphHierarchyEdge>( 4); } // The vertices passed in were ordered, check that the // target cell has not already been marked as visited if (internalTargetCell.temp[0] == 1) { // Internal Edge is leftward, reverse it internalEdge.invert(); // There must be a connectsAsSource list already internalTargetCell.connectsAsSource .add(internalEdge); tempList.add(internalEdge); internalVertices[i].connectsAsTarget .add(internalEdge); } else { internalTargetCell.connectsAsTarget .add(internalEdge); } } } } // Remove the inverted edges as sources from this node Iterator<mxGraphHierarchyEdge> iter2 = tempList.iterator(); while (iter2.hasNext()) { internalVertices[i].connectsAsSource.remove(iter2.next()); } tempList.clear(); // Use the temp variable in the internal nodes to mark this // internal vertex as having been visited. internalVertices[i].temp[0] = 1; } } /** * Creates all edges in the internal model * * @param layout * reference to the layout algorithm * @param vertices * the vertices whom are to have an internal representation * created * @param internalVertices * the blank internal vertices to have their information filled * in using the real vertices */ protected void createInternalCells(mxHierarchicalLayout layout, Object[] vertices, mxGraphHierarchyNode[] internalVertices) { mxGraph graph = layout.getGraph(); // Create internal edges for (int i = 0; i < vertices.length; i++) { internalVertices[i] = new mxGraphHierarchyNode(vertices[i]); vertexMapper.put(vertices[i], internalVertices[i]); // If the layout is deterministic, order the cells Object[] conns = graph.getConnections(vertices[i], parent); List<Object> outgoingCells = Arrays.asList(graph.getOpposites( conns, vertices[i])); internalVertices[i].connectsAsSource = new LinkedHashSet<mxGraphHierarchyEdge>( outgoingCells.size()); // Create internal edges, but don't do any rank assignment yet // First use the information from the greedy cycle remover to // invert the leftward edges internally Iterator<Object> iter = outgoingCells.iterator(); while (iter.hasNext()) { // Don't add self-loops Object cell = iter.next(); if (cell != vertices[i] && graph.getModel().isVertex(cell) && !layout.isVertexIgnored(cell)) { // Allow for parallel edges Object[] edges = graph.getEdgesBetween(vertices[i], cell, true); if (edges != null && edges.length > 0) { ArrayList<Object> listEdges = new ArrayList<Object>( edges.length); for (int j = 0; j < edges.length; j++) { listEdges.add(edges[j]); } mxGraphHierarchyEdge internalEdge = new mxGraphHierarchyEdge( listEdges); Iterator<Object> iter2 = listEdges.iterator(); while (iter2.hasNext()) { Object edge = iter2.next(); edgeMapper.put(edge, internalEdge); // Resets all point on the edge and disables the edge style // without deleting it from the cell style graph.resetEdge(edge); if (layout.isDisableEdgeStyle()) { layout.setEdgeStyleEnabled(edge, false); layout.setOrthogonalEdge(edge,true); } } internalEdge.source = internalVertices[i]; internalVertices[i].connectsAsSource.add(internalEdge); } } } // Ensure temp variable is cleared from any previous use internalVertices[i].temp[0] = 0; } } /** * Basic determination of minimum layer ranking by working from from sources * or sinks and working through each node in the relevant edge direction. * Starting at the sinks is basically a longest path layering algorithm. */ public void initialRank() { Collection<mxGraphHierarchyNode> internalNodes = vertexMapper.values(); LinkedList<mxGraphHierarchyNode> startNodes = new LinkedList<mxGraphHierarchyNode>(); if (!scanRanksFromSinks && roots != null) { Iterator<Object> iter = roots.iterator(); while (iter.hasNext()) { mxGraphHierarchyNode internalNode = vertexMapper.get(iter .next()); if (internalNode != null) { startNodes.add(internalNode); } } } if (scanRanksFromSinks) { Iterator<mxGraphHierarchyNode> iter = internalNodes.iterator(); while (iter.hasNext()) { mxGraphHierarchyNode internalNode = iter.next(); if (internalNode.connectsAsSource == null || internalNode.connectsAsSource.isEmpty()) { startNodes.add(internalNode); } } } if (startNodes.isEmpty()) { // Start list from sources Iterator<mxGraphHierarchyNode> iter = internalNodes.iterator(); while (iter.hasNext()) { mxGraphHierarchyNode internalNode = iter.next(); if (internalNode.connectsAsTarget == null || internalNode.connectsAsTarget.isEmpty()) { startNodes.add(internalNode); } } } Iterator<mxGraphHierarchyNode> iter = internalNodes.iterator(); while (iter.hasNext()) { mxGraphHierarchyNode internalNode = iter.next(); // Mark the node as not having had a layer assigned internalNode.temp[0] = -1; } List<mxGraphHierarchyNode> startNodesCopy = new ArrayList<mxGraphHierarchyNode>( startNodes); while (!startNodes.isEmpty()) { mxGraphHierarchyNode internalNode = startNodes.getFirst(); Collection<mxGraphHierarchyEdge> layerDeterminingEdges; Collection<mxGraphHierarchyEdge> edgesToBeMarked; if (scanRanksFromSinks) { layerDeterminingEdges = internalNode.connectsAsSource; edgesToBeMarked = internalNode.connectsAsTarget; } else { layerDeterminingEdges = internalNode.connectsAsTarget; edgesToBeMarked = internalNode.connectsAsSource; } // flag to keep track of whether or not all layer determining // edges have been scanned boolean allEdgesScanned = true; // Work out the layer of this node from the layer determining // edges Iterator<mxGraphHierarchyEdge> iter2 = layerDeterminingEdges .iterator(); // The minimum layer number of any node connected by one of // the layer determining edges variable. If we are starting // from sources, need to start at some huge value and // normalise down afterwards int minimumLayer = 0; if (!scanRanksFromSinks) { minimumLayer = SOURCESCANSTARTRANK; } while (allEdgesScanned && iter2.hasNext()) { mxGraphHierarchyEdge internalEdge = iter2.next(); if (internalEdge.temp[0] == 5270620) { // This edge has been scanned, get the layer of the // node on the other end mxGraphHierarchyNode otherNode; if (scanRanksFromSinks) { otherNode = internalEdge.target; } else { otherNode = internalEdge.source; } if (scanRanksFromSinks) { minimumLayer = Math.max(minimumLayer, otherNode.temp[0] + 1); } else { minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1); } } else { allEdgesScanned = false; } } // If all edge have been scanned, assign the layer, mark all // edges in the other direction and remove from the nodes list if (allEdgesScanned) { internalNode.temp[0] = minimumLayer; if (scanRanksFromSinks) { maxRank = Math.max(maxRank, minimumLayer); } else { maxRank = Math.min(maxRank, minimumLayer); } if (edgesToBeMarked != null) { Iterator<mxGraphHierarchyEdge> iter3 = edgesToBeMarked .iterator(); while (iter3.hasNext()) { mxGraphHierarchyEdge internalEdge = iter3 .next(); // Assign unique stamp ( y/m/d/h ) internalEdge.temp[0] = 5270620; // Add node on other end of edge to LinkedList of // nodes to be analysed mxGraphHierarchyNode otherNode; if (scanRanksFromSinks) { otherNode = internalEdge.source; } else { otherNode = internalEdge.target; } // Only add node if it hasn't been assigned a layer if (otherNode.temp[0] == -1) { startNodes.addLast(otherNode); // Mark this other node as neither being // unassigned nor assigned so it isn't // added to this list again, but it's // layer isn't used in any calculation. otherNode.temp[0] = -2; } } } startNodes.removeFirst(); } else { // Not all the edges have been scanned, get to the back of // the class and put the dunces cap on Object removedCell = startNodes.removeFirst(); startNodes.addLast(internalNode); if (removedCell == internalNode && startNodes.size() == 1) { // This is an error condition, we can't get out of // this loop. It could happen for more than one node // but that's a lot harder to detect. Log the error // TODO make log comment break; } } } if (scanRanksFromSinks) { // Tighten the rank 0 nodes as far as possible for (int i = 0; i < startNodesCopy.size(); i++) { mxGraphHierarchyNode internalNode = startNodesCopy .get(i); int currentMinLayer = 1000000; Iterator<mxGraphHierarchyEdge> iter2 = internalNode.connectsAsTarget .iterator(); while (iter2.hasNext()) { mxGraphHierarchyEdge internalEdge = iter2 .next(); mxGraphHierarchyNode otherNode = internalEdge.source; internalNode.temp[0] = Math.min(currentMinLayer, otherNode.temp[0] - 1); currentMinLayer = internalNode.temp[0]; } } } else { // Normalize the ranks down from their large starting value to place // at least 1 sink on layer 0 iter = internalNodes.iterator(); while (iter.hasNext()) { mxGraphHierarchyNode internalNode = iter .next(); // Mark the node as not having had a layer assigned internalNode.temp[0] -= maxRank; } // Reset the maxRank to that which would be expected for a from-sink // scan maxRank = SOURCESCANSTARTRANK - maxRank; } } /** * Fixes the layer assignments to the values stored in the nodes. Also needs * to create dummy nodes for edges that cross layers. */ public void fixRanks() { final mxGraphHierarchyRank[] rankList = new mxGraphHierarchyRank[maxRank + 1]; ranks = new LinkedHashMap<Integer, mxGraphHierarchyRank>(maxRank + 1); for (int i = 0; i < maxRank + 1; i++) { rankList[i] = new mxGraphHierarchyRank(); ranks.put(new Integer(i), rankList[i]); } // Perform a DFS to obtain an initial ordering for each rank. // Without doing this you would end up having to process // crossings for a standard tree. mxGraphHierarchyNode[] rootsArray = null; if (roots != null) { Object[] oldRootsArray = roots.toArray(); rootsArray = new mxGraphHierarchyNode[oldRootsArray.length]; for (int i = 0; i < oldRootsArray.length; i++) { Object node = oldRootsArray[i]; mxGraphHierarchyNode internalNode = vertexMapper.get(node); rootsArray[i] = internalNode; } } visit(new mxGraphHierarchyModel.CellVisitor() { public void visit(mxGraphHierarchyNode parent, mxGraphHierarchyNode cell, mxGraphHierarchyEdge connectingEdge, int layer, int seen) { mxGraphHierarchyNode node = cell; if (seen == 0 && node.maxRank < 0 && node.minRank < 0) { rankList[node.temp[0]] .add(cell); node.maxRank = node.temp[0]; node.minRank = node.temp[0]; // Set temp[0] to the nodes position in the rank node.temp[0] = rankList[node.maxRank].size() - 1; } if (parent != null && connectingEdge != null) { int parentToCellRankDifference = (parent).maxRank - node.maxRank; if (parentToCellRankDifference > 1) { // There are ranks in between the parent and current cell mxGraphHierarchyEdge edge = connectingEdge; edge.maxRank = (parent).maxRank; edge.minRank = (cell).maxRank; edge.temp = new int[parentToCellRankDifference - 1]; edge.x = new double[parentToCellRankDifference - 1]; edge.y = new double[parentToCellRankDifference - 1]; for (int i = edge.minRank + 1; i < edge.maxRank; i++) { // The connecting edge must be added to the // appropriate ranks rankList[i].add(edge); edge.setGeneralPurposeVariable(i, rankList[i] .size() - 1); } } } } }, rootsArray, false, null); } /** * A depth first search through the internal hierarchy model * * @param visitor * the visitor pattern to be called for each node * @param trackAncestors * whether or not the search is to keep track all nodes directly * above this one in the search path */ public void visit(CellVisitor visitor, mxGraphHierarchyNode[] dfsRoots, boolean trackAncestors, Set<mxGraphHierarchyNode> seenNodes) { // Run dfs through on all roots if (dfsRoots != null) { for (int i = 0; i < dfsRoots.length; i++) { mxGraphHierarchyNode internalNode = dfsRoots[i]; if (internalNode != null) { if (seenNodes == null) { seenNodes = new HashSet<mxGraphHierarchyNode>(); } if (trackAncestors) { // Set up hash code for root internalNode.hashCode = new int[2]; internalNode.hashCode[0] = dfsCount; internalNode.hashCode[1] = i; dfs(null, internalNode, null, visitor, seenNodes, internalNode.hashCode, i, 0); } else { dfs(null, internalNode, null, visitor, seenNodes, 0); } } } dfsCount++; } } /** * Performs a depth first search on the internal hierarchy model * * @param parent * the parent internal node of the current internal node * @param root * the current internal node * @param connectingEdge * the internal edge connecting the internal node and the parent * internal node, if any * @param visitor * the visitor pattern to be called for each node * @param seen * a set of all nodes seen by this dfs a set of all of the * ancestor node of the current node * @param layer * the layer on the dfs tree ( not the same as the model ranks ) */ public void dfs(mxGraphHierarchyNode parent, mxGraphHierarchyNode root, mxGraphHierarchyEdge connectingEdge, CellVisitor visitor, Set<mxGraphHierarchyNode> seen, int layer) { if (root != null) { if (!seen.contains(root)) { visitor.visit(parent, root, connectingEdge, layer, 0); seen.add(root); // Copy the connects as source list so that visitors // can change the original for edge direction inversions final Object[] outgoingEdges = root.connectsAsSource.toArray(); for (int i = 0; i < outgoingEdges.length; i++) { mxGraphHierarchyEdge internalEdge = (mxGraphHierarchyEdge) outgoingEdges[i]; mxGraphHierarchyNode targetNode = internalEdge.target; // Root check is O(|roots|) dfs(root, targetNode, internalEdge, visitor, seen, layer + 1); } } else { // Use the int field to indicate this node has been seen visitor.visit(parent, root, connectingEdge, layer, 1); } } } /** * Performs a depth first search on the internal hierarchy model. This dfs * extends the default version by keeping track of cells ancestors, but it * should be only used when necessary because of it can be computationally * intensive for deep searches. * * @param parent * the parent internal node of the current internal node * @param root * the current internal node * @param connectingEdge * the internal edge connecting the internal node and the parent * internal node, if any * @param visitor * the visitor pattern to be called for each node * @param seen * a set of all nodes seen by this dfs * @param ancestors * the parent hash code * @param childHash * the new hash code for this node * @param layer * the layer on the dfs tree ( not the same as the model ranks ) */ public void dfs(mxGraphHierarchyNode parent, mxGraphHierarchyNode root, mxGraphHierarchyEdge connectingEdge, CellVisitor visitor, Set<mxGraphHierarchyNode> seen, int[] ancestors, int childHash, int layer) { // Explanation of custom hash set. Previously, the ancestors variable // was passed through the dfs as a HashSet. The ancestors were copied // into a new HashSet and when the new child was processed it was also // added to the set. If the current node was in its ancestor list it // meant there is a cycle in the graph and this information is passed // to the visitor.visit() in the seen parameter. The HashSet clone was // very expensive on CPU so a custom hash was developed using primitive // types. temp[] couldn't be used so hashCode[] was added to each node. // Each new child adds another int to the array, copying the prefix // from its parent. Child of the same parent add different ints (the // limit is therefore 2^32 children per parent...). If a node has a // child with the hashCode already set then the child code is compared // to the same portion of the current nodes array. If they match there // is a loop. // Note that the basic mechanism would only allow for 1 use of this // functionality, so the root nodes have two ints. The second int is // incremented through each node root and the first is incremented // through each run of the dfs algorithm (therefore the dfs is not // thread safe). The hash code of each node is set if not already set, // or if the first int does not match that of the current run. if (root != null) { if (parent != null) { // Form this nodes hash code if necessary, that is, if the // hashCode variable has not been initialized or if the // start of the parent hash code does not equal the start of // this nodes hash code, indicating the code was set on a // previous run of this dfs. if (root.hashCode == null || root.hashCode[0] != parent.hashCode[0]) { int hashCodeLength = parent.hashCode.length + 1; root.hashCode = new int[hashCodeLength]; System.arraycopy(parent.hashCode, 0, root.hashCode, 0, parent.hashCode.length); root.hashCode[hashCodeLength - 1] = childHash; } } if (!seen.contains(root)) { visitor.visit(parent, root, connectingEdge, layer, 0); seen.add(root); // Copy the connects as source list so that visitors // can change the original for edge direction inversions final Object[] outgoingEdges = root.connectsAsSource.toArray(); for (int i = 0; i < outgoingEdges.length; i++) { mxGraphHierarchyEdge internalEdge = (mxGraphHierarchyEdge) outgoingEdges[i]; mxGraphHierarchyNode targetNode = internalEdge.target; // Root check is O(|roots|) dfs(root, targetNode, internalEdge, visitor, seen, root.hashCode, i, layer + 1); } } else { // Use the int field to indicate this node has been seen visitor.visit(parent, root, connectingEdge, layer, 1); } } } /** * Defines the interface that visitors use to perform operations upon the * graph information during depth first search (dfs) or other tree-traversal * strategies implemented by subclassers. */ public interface CellVisitor { /** * The method within which the visitor will perform operations upon the * graph model * * @param parent * the parent cell the current cell * @param cell * the current cell visited * @param connectingEdge * the edge that led the last cell visited to this cell * @param layer * the current layer of the tree * @param seen * an int indicating whether this cell has been seen * previously */ public void visit(mxGraphHierarchyNode parent, mxGraphHierarchyNode cell, mxGraphHierarchyEdge connectingEdge, int layer, int seen); } /** * @return Returns the vertexMapping. */ public Map<Object, mxGraphHierarchyNode> getVertexMapper() { if (vertexMapper == null) { vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>(); } return vertexMapper; } /** * @param vertexMapping * The vertexMapping to set. */ public void setVertexMapper(Map<Object, mxGraphHierarchyNode> vertexMapping) { this.vertexMapper = vertexMapping; } /** * @return Returns the edgeMapper. */ public Map<Object, mxGraphHierarchyEdge> getEdgeMapper() { return edgeMapper; } /** * @param edgeMapper * The edgeMapper to set. */ public void setEdgeMapper(Map<Object, mxGraphHierarchyEdge> edgeMapper) { this.edgeMapper = edgeMapper; } /** * @return Returns the dfsCount. */ public int getDfsCount() { return dfsCount; } /** * @param dfsCount * The dfsCount to set. */ public void setDfsCount(int dfsCount) { this.dfsCount = dfsCount; } /** * @return Returns the deterministic. */ public boolean isDeterministic() { return deterministic; } /** * @param deterministic * The deterministic to set. */ public void setDeterministic(boolean deterministic) { this.deterministic = deterministic; } }