/*
* 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.client.widgets.explorer.tree;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.annotation.PostConstruct;
import javax.enterprise.context.Dependent;
import javax.enterprise.event.Event;
import javax.enterprise.event.Observes;
import javax.inject.Inject;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import org.jboss.errai.ioc.client.api.ManagedInstance;
import org.kie.workbench.common.stunner.core.api.DefinitionManager;
import org.kie.workbench.common.stunner.core.client.canvas.Canvas;
import org.kie.workbench.common.stunner.core.client.canvas.CanvasHandler;
import org.kie.workbench.common.stunner.core.client.canvas.event.AbstractCanvasEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.AbstractCanvasHandlerEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.CanvasClearEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.registration.CanvasElementAddedEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.registration.CanvasElementRemovedEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.registration.CanvasElementUpdatedEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.registration.CanvasElementsClearEvent;
import org.kie.workbench.common.stunner.core.client.canvas.event.selection.CanvasElementSelectedEvent;
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.kie.workbench.common.stunner.core.graph.content.definition.DefinitionSet;
import org.kie.workbench.common.stunner.core.graph.content.relationship.Child;
import org.kie.workbench.common.stunner.core.graph.processing.traverse.content.AbstractChildrenTraverseCallback;
import org.kie.workbench.common.stunner.core.graph.processing.traverse.content.ChildrenTraverseProcessor;
import org.uberfire.client.mvp.UberView;
// TODO: Use incremental updates, do not visit whole graph on each model update.
@Dependent
public class TreeExplorer implements IsWidget {
private static Logger LOGGER = Logger.getLogger(TreeExplorer.class.getName());
public interface View extends UberView<TreeExplorer> {
View addItem(final String uuid,
final IsWidget itemView,
final boolean state);
View addItem(final String uuid,
final IsWidget itemView,
final boolean state,
final int... parentIdx);
View removeItem(final int index);
View removeItem(final int index,
final int... parentIdx);
View clear();
}
private DefinitionManager definitionManager;
private ChildrenTraverseProcessor childrenTraverseProcessor;
private ManagedInstance<TreeExplorerItem> treeExplorerItemInstances;
private Event<CanvasElementSelectedEvent> elementSelectedEventEvent;
private View view;
//ManagedInstance<T> releases instances when TreeExplorer is destroyed; therefore when
//nodes are added, removed or updated whilst the instance of TreeExplorer is active
//more and more instances of TreeExplorerItem are created. TreeExplorerItem's view
//includes a Glyph which can lead to the DOM being flooded with <img..> elements
//unless we destroy instances when the TreeExplorer is refreshed.
private Set<TreeExplorerItem> treeExplorerItems = new HashSet<>();
private CanvasHandler canvasHandler;
@Inject
public TreeExplorer(final DefinitionManager definitionManager,
final ChildrenTraverseProcessor childrenTraverseProcessor,
final ManagedInstance<TreeExplorerItem> treeExplorerItemInstances,
final Event<CanvasElementSelectedEvent> elementSelectedEventEvent,
final View view) {
this.definitionManager = definitionManager;
this.childrenTraverseProcessor = childrenTraverseProcessor;
this.treeExplorerItemInstances = treeExplorerItemInstances;
this.elementSelectedEventEvent = elementSelectedEventEvent;
this.view = view;
}
@PostConstruct
public void init() {
view.init(this);
}
@SuppressWarnings("unchecked")
public void show(final CanvasHandler canvasHandler) {
this.canvasHandler = canvasHandler;
if (null != canvasHandler && null != canvasHandler.getDiagram()) {
doShow(canvasHandler.getDiagram().getGraph());
}
}
private void doShow(final Graph<org.kie.workbench.common.stunner.core.graph.content.view.View, Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge>> graph) {
traverseChildrenEdges(graph,
true);
}
private void traverseChildrenEdges(final Graph<org.kie.workbench.common.stunner.core.graph.content.view.View, Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge>> graph,
final boolean expand) {
assert graph != null;
clear();
childrenTraverseProcessor.traverse(graph,
new AbstractChildrenTraverseCallback<Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge>, Edge<Child, Node>>() {
Node parent = null;
int level = 0;
final List<Integer> levelIdx = new LinkedList<Integer>();
@Override
public void startEdgeTraversal(final Edge<Child, Node> edge) {
super.startEdgeTraversal(edge);
final Node newParent = edge.getSourceNode();
assert newParent != null;
if (null == parent || (!parent.equals(newParent))) {
level++;
}
this.parent = edge.getSourceNode();
}
@Override
public void endEdgeTraversal(final Edge<Child, Node> edge) {
super.endEdgeTraversal(edge);
final Node newParent = edge.getSourceNode();
assert newParent != null;
if (!parent.equals(newParent)) {
level--;
this.parent = newParent;
}
}
@Override
public void startGraphTraversal(final Graph<DefinitionSet, Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge>> graph) {
super.startGraphTraversal(graph);
levelIdx.clear();
levelIdx.add(-1);
}
@Override
public boolean startNodeTraversal(final List<Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge>> parents,
final Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge> node) {
super.startNodeTraversal(parents,
node);
onStartNodeTraversal(node);
return true;
}
@Override
public void startNodeTraversal(final Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge> node) {
super.startNodeTraversal(node);
onStartNodeTraversal(node);
}
private void onStartNodeTraversal(final Node<org.kie.workbench.common.stunner.core.graph.content.view.View, Edge> node) {
super.startNodeTraversal(node);
inc(levelIdx,
level);
if (null == parent) {
final TreeExplorerItem item = treeExplorerItemInstances.get();
treeExplorerItems.add(item);
view.addItem(node.getUUID(),
item.asWidget(),
expand);
item.show(getShapeSetId(),
node);
} else {
int[] parentsIdx = getParentsIdx(levelIdx,
level);
final TreeExplorerItem item = treeExplorerItemInstances.get();
treeExplorerItems.add(item);
view.addItem(node.getUUID(),
item.asWidget(),
expand,
parentsIdx);
item.show(getShapeSetId(),
node);
}
}
});
}
private void inc(final List<Integer> levels,
final int level) {
if (levels.size() < (level + 1)) {
levels.add(0);
} else {
final int idx = levels.get(level);
levels.set(level,
idx + 1);
}
}
private int[] getParentsIdx(final List<Integer> idxList,
final int maxLevel) {
if (!idxList.isEmpty()) {
final int targetPos = (idxList.size() - (idxList.size() - maxLevel)) + 1;
final int[] resultArray = new int[targetPos];
for (int x = 0; x < targetPos; x++) {
resultArray[x] = idxList.get(x);
}
return resultArray;
}
return new int[]{};
}
public void clear() {
//Destroy existing TreeExplorerItems; that otherwise are not GC'ed until TreeExplorer closes.
treeExplorerItems.forEach(TreeExplorerItem::destroy);
treeExplorerItems.clear();
view.clear();
}
void onSelect(final String uuid) {
selectShape(canvasHandler.getCanvas(),
uuid);
}
private void selectShape(final Canvas canvas,
final String uuid) {
elementSelectedEventEvent.fire(new CanvasElementSelectedEvent(canvasHandler,
uuid));
}
void onCanvasClearEvent(@Observes CanvasClearEvent canvasClearEvent) {
if (null != canvasHandler &&
null != canvasHandler.getCanvas() &&
canvasHandler.getCanvas().equals(canvasClearEvent.getCanvas())) {
clear();
}
}
void onCanvasElementAddedEvent(final @Observes CanvasElementAddedEvent canvasElementAddedEvent) {
if (checkEventContext(canvasElementAddedEvent)) {
showEventGraph(canvasElementAddedEvent);
}
}
void onCanvasElementRemovedEvent(final @Observes CanvasElementRemovedEvent elementRemovedEvent) {
if (checkEventContext(elementRemovedEvent)) {
showEventGraph(elementRemovedEvent);
}
}
void onCanvasElementsClearEvent(final @Observes CanvasElementsClearEvent canvasClearEvent) {
if (checkEventContext(canvasClearEvent)) {
showEventGraph(canvasClearEvent);
}
}
void onCanvasElementUpdatedEvent(final @Observes CanvasElementUpdatedEvent canvasElementUpdatedEvent) {
if (checkEventContext(canvasElementUpdatedEvent)) {
showEventGraph(canvasElementUpdatedEvent);
}
}
private boolean checkEventContext(final AbstractCanvasHandlerEvent canvasHandlerEvent) {
final CanvasHandler _canvasHandler = canvasHandlerEvent.getCanvasHandler();
return canvasHandler != null && canvasHandler.equals(_canvasHandler);
}
private boolean checkEventContext(final AbstractCanvasEvent canvasEvent) {
final Canvas canvas = canvasEvent.getCanvas();
return null != canvasHandler && null != canvasHandler.getCanvas()
&& canvasHandler.getCanvas().equals(canvas);
}
private String getShapeSetId() {
return canvasHandler.getDiagram().getMetadata().getShapeSetId();
}
@SuppressWarnings("unchecked")
private void showEventGraph(final AbstractCanvasHandlerEvent canvasHandlerEvent) {
doShow(canvasHandlerEvent.getCanvasHandler().getDiagram().getGraph());
}
@Override
public Widget asWidget() {
return view.asWidget();
}
public CanvasHandler getCanvasHandler() {
return canvasHandler;
}
}