/* * Copyright 2017 Red Hat, Inc. and/or its affiliates. * * 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.kie.workbench.common.stunner.core.graph.processing.traverse.tree; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Predicate; import javax.enterprise.context.Dependent; import org.kie.workbench.common.stunner.core.graph.Edge; import org.kie.workbench.common.stunner.core.graph.Graph; import org.kie.workbench.common.stunner.core.graph.Node; import org.uberfire.mvp.Command; @Dependent public final class TreeWalkTraverseProcessorImpl implements TreeWalkTraverseProcessor { private Graph graph; private EdgeVisitorPolicy edgeVisitorPolicy; private TreeTraverseCallback<Graph, Node, Edge> callback; private final Set<String> processesEdges = new HashSet<String>(); private final Set<String> processesNodes = new HashSet<String>(); private final Set<Edge> pendingEdges = new HashSet<Edge>(); private Predicate<Node<?, Edge>> startNodePredicate; public TreeWalkTraverseProcessorImpl() { this.edgeVisitorPolicy = EdgeVisitorPolicy.VISIT_EDGE_BEFORE_TARGET_NODE; this.startNodePredicate = n -> n.getInEdges().isEmpty(); } @Override public TreeWalkTraverseProcessorImpl useEdgeVisitorPolicy(final EdgeVisitorPolicy policy) { this.edgeVisitorPolicy = policy; return this; } @Override public TreeWalkTraverseProcessorImpl useStartNodePredicate(final Predicate<Node<?, Edge>> predicate) { this.startNodePredicate = predicate; return this; } @Override @SuppressWarnings("unchecked") public void traverse(final Graph graph, final Node node, final TreeTraverseCallback<Graph, Node, Edge> callback) { startTraverse(graph, Optional.ofNullable(node), callback); } @Override public void traverse(final Graph graph, final TreeTraverseCallback<Graph, Node, Edge> callback) { startTraverse(graph, Optional.empty(), callback); } private void startTraverse(final Graph graph, final Optional<Node<?, Edge>> node, final TreeTraverseCallback<Graph, Node, Edge> callback) { assert graph != null && callback != null; this.graph = graph; this.callback = callback; // Clear instance's caches state. processesNodes.clear(); processesEdges.clear(); pendingEdges.clear(); // Start traversing the graph. startGraphTraversal(node); // Process any remaining edges, if any. processPendingEdges(); // End the graph traversal. endGraphTraversal(); // Clear instance's state. this.processesEdges.clear(); this.pendingEdges.clear(); this.processesNodes.clear(); this.graph = null; this.callback = null; } @SuppressWarnings("unchecked") private void startGraphTraversal(final Optional<Node<?, Edge>> startNode) { callback.startGraphTraversal(graph); if (!startNode.isPresent()) { final List<Node<?, Edge>> orderedGraphNodes = getStartingNodes(); for (final Node<?, Edge> node : orderedGraphNodes) { ifNotProcessed(node, () -> startNodeTraversal(node)); } } else { startNodeTraversal(startNode.get()); } } private void endGraphTraversal() { callback.endGraphTraversal(); } private void processPendingEdges() { pendingEdges.forEach(this::processPendingEdge); } private void processPendingEdge(final Edge edge) { startEdgeTraversal(edge); } private boolean isEdgeProcessed(final Edge edge) { return processesEdges.contains(edge.getUUID()); } @SuppressWarnings("unchecked") private void startNodeTraversal(final Node<?, Edge> node) { this.processesNodes.add(node.getUUID()); if (callback.startNodeTraversal(node)) { // Outgoing connections. node.getOutEdges().forEach(this::startEdgeTraversal); callback.endNodeTraversal(node); // Outgoing connections. pendingEdges.addAll(node.getInEdges()); } } @SuppressWarnings("unchecked") private void startEdgeTraversal(final Edge edge) { final String uuid = edge.getUUID(); if (!this.processesEdges.contains(uuid)) { processesEdges.add(uuid); boolean isTraverNode = true; if (isVisitBefore()) { isTraverNode = callback.startEdgeTraversal(edge); } if (isTraverNode) { ifNotProcessed(edge.getTargetNode(), () -> startNodeTraversal(edge.getTargetNode())); } if (isVisitAfter()) { callback.startEdgeTraversal(edge); } endEdgeTraversal(edge); } } private void endEdgeTraversal(final Edge edge) { callback.endEdgeTraversal(edge); } private boolean isVisitBefore() { return EdgeVisitorPolicy.VISIT_EDGE_BEFORE_TARGET_NODE.equals(edgeVisitorPolicy); } private boolean isVisitAfter() { return !isVisitBefore(); } private void ifNotProcessed(final Node node, final Command action) { if (null != node && !processesNodes.contains(node.getUUID())) { action.execute(); } } @SuppressWarnings("unchecked") private List<Node<?, Edge>> getStartingNodes() { final Iterable<Node> nodes = graph.nodes(); final List<Node<?, Edge>> result = new LinkedList<>(); nodes.forEach(n -> { if (isStartingNode(n)) { result.add(n); } }); return result; } @SuppressWarnings("unchecked") private boolean isStartingNode(final Node node) { return null == node.getInEdges() || startNodePredicate.test(node); } }