org.thechiselgroup.choosel.visualization_component.graph.client.Graph.java Source code

Java tutorial

Introduction

Here is the source code for org.thechiselgroup.choosel.visualization_component.graph.client.Graph.java

Source

/*******************************************************************************
 * Copyright 2009, 2010 Lars Grammel 
 *
 * 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.thechiselgroup.choosel.visualization_component.graph.client;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.thechiselgroup.choosel.core.client.command.CommandManager;
import org.thechiselgroup.choosel.core.client.geometry.DefaultSize;
import org.thechiselgroup.choosel.core.client.geometry.Point;
import org.thechiselgroup.choosel.core.client.geometry.Size;
import org.thechiselgroup.choosel.core.client.persistence.Memento;
import org.thechiselgroup.choosel.core.client.persistence.PersistableRestorationService;
import org.thechiselgroup.choosel.core.client.resources.DefaultResourceSet;
import org.thechiselgroup.choosel.core.client.resources.Resource;
import org.thechiselgroup.choosel.core.client.resources.ResourceCategorizer;
import org.thechiselgroup.choosel.core.client.resources.ResourceManager;
import org.thechiselgroup.choosel.core.client.resources.ResourceSet;
import org.thechiselgroup.choosel.core.client.resources.UnionResourceSet;
import org.thechiselgroup.choosel.core.client.resources.persistence.ResourceSetAccessor;
import org.thechiselgroup.choosel.core.client.resources.persistence.ResourceSetCollector;
import org.thechiselgroup.choosel.core.client.ui.SidePanelSection;
import org.thechiselgroup.choosel.core.client.util.DataType;
import org.thechiselgroup.choosel.core.client.util.NoSuchAdapterException;
import org.thechiselgroup.choosel.core.client.util.collections.CollectionFactory;
import org.thechiselgroup.choosel.core.client.util.collections.Delta;
import org.thechiselgroup.choosel.core.client.util.collections.LightweightCollection;
import org.thechiselgroup.choosel.core.client.visualization.model.AbstractViewContentDisplay;
import org.thechiselgroup.choosel.core.client.visualization.model.Slot;
import org.thechiselgroup.choosel.core.client.visualization.model.ViewContentDisplayCallback;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItem;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemContainer;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemInteraction;
import org.thechiselgroup.choosel.core.client.visualization.model.VisualItemInteraction.Type;
import org.thechiselgroup.choosel.core.client.visualization.model.extensions.RequiresAutomaticResourceSet;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphDisplay;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphDisplayLoadingFailureEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphDisplayLoadingFailureEventHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphDisplayReadyEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphDisplayReadyEventHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphLayouts;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.GraphWidget;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.Node;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeDragEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeDragHandleMouseDownEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeDragHandleMouseDownHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeDragHandleMouseMoveEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeDragHandleMouseMoveHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeDragHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMenuItemClickedHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMouseClickEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMouseClickHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMouseOutEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMouseOutHandler;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMouseOverEvent;
import org.thechiselgroup.choosel.visualization_component.graph.client.widget.NodeMouseOverHandler;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;

// TODO separate out ncbo specific stuff and service calls
// TODO register listener for double click on node --> change expansion state
public class Graph extends AbstractViewContentDisplay
        implements RequiresAutomaticResourceSet, GraphLayoutSupport, GraphLayoutCallback {

    public static class DefaultDisplay extends GraphWidget implements GraphDisplay {

        // TODO why is size needed in the first place??
        public DefaultDisplay() {
            this(400, 300);
        }

        public DefaultDisplay(int width, int height) {
            super(width, height);
        }

    }

    private class GraphEventHandler implements NodeMouseOverHandler, NodeMouseOutHandler, NodeMouseClickHandler,
            MouseMoveHandler, NodeDragHandler, NodeDragHandleMouseDownHandler, NodeDragHandleMouseMoveHandler {

        /**
         * Node that the mouse is currently over. Set by mouse out and mouse
         * over. Not over a node if null.
         */
        private Node currentNode = null;

        @Override
        public void onDrag(NodeDragEvent event) {
            commandManager.execute(new MoveNodeCommand(graphDisplay, event.getNode(),
                    new Point(event.getStartX(), event.getStartY()), new Point(event.getEndX(), event.getEndY())));
        }

        @Override
        public void onMouseClick(NodeMouseClickEvent event) {
            reportInteraction(Type.CLICK, event);
        }

        @Override
        public void onMouseDown(NodeDragHandleMouseDownEvent event) {
            reportInteraction(Type.MOUSE_DOWN, event);
        }

        @Override
        public void onMouseMove(MouseMoveEvent event) {
            if (currentNode != null) {
                getVisualItem(currentNode).reportInteraction(
                        new VisualItemInteraction(Type.MOUSE_MOVE, event.getClientX(), event.getClientY()));
            }
        }

        @Override
        public void onMouseMove(NodeDragHandleMouseMoveEvent event) {
            reportInteraction(Type.MOUSE_MOVE, event);
        }

        @Override
        public void onMouseOut(NodeMouseOutEvent event) {
            currentNode = null;
            reportInteraction(Type.MOUSE_OUT, event);
        }

        @Override
        public void onMouseOver(NodeMouseOverEvent event) {
            currentNode = event.getNode();
            reportInteraction(Type.MOUSE_OVER, event);
        }

        private void reportInteraction(Type eventType, NodeEvent<?> event) {
            int clientX = event.getMouseX() + asWidget().getAbsoluteLeft();
            int clientY = event.getMouseY() + asWidget().getAbsoluteTop();

            getVisualItem(event).reportInteraction(new VisualItemInteraction(eventType, clientX, clientY));
        }

    }

    public class GraphLayoutAction implements ViewContentDisplayAction {

        private String layout;

        public GraphLayoutAction(String layout) {
            this.layout = layout;
        }

        @Override
        public void execute() {
            commandManager.execute(new GraphLayoutCommand(graphDisplay, layout, getAllNodes()));
        }

        @Override
        public String getLabel() {
            return layout;
        }
    }

    public static final Slot NODE_BORDER_COLOR = new Slot("nodeBorderColor", "Node Border Color", DataType.COLOR);

    public static final Slot NODE_BACKGROUND_COLOR = new Slot("nodeBackgroundColor", "Node Color", DataType.COLOR);

    public static final Slot NODE_LABEL_SLOT = new Slot("nodeLabel", "Node Label", DataType.TEXT);

    public final static String ID = "org.thechiselgroup.choosel.visualization_component.graph.Graph";

    private static final String MEMENTO_ARC_ITEM_CONTAINERS_CHILD = "arcItemContainers";

    private static final String MEMENTO_NODE_LOCATIONS_CHILD = "nodeLocations";

    private static final String MEMENTO_X = "x";

    private static final String MEMENTO_Y = "y";

    // TODO move
    public static String getArcId(String arcType, String sourceId, String targetId) {
        // FIXME this needs escaping of special characters to work properly
        return arcType + ":" + sourceId + "_" + targetId;
    }

    private ArcTypeProvider arcStyleProvider;

    private final CommandManager commandManager;

    // advanced node class: (incoming, outgoing, expanded: state machine)

    private final GraphDisplay graphDisplay;

    private boolean ready = false;

    private GraphExpansionRegistry registry;

    private ResourceCategorizer resourceCategorizer;

    private ResourceManager resourceManager;

    private UnionResourceSet nodeResources = new UnionResourceSet(new DefaultResourceSet());

    private Map<String, ArcItemContainer> arcItemContainersByArcTypeID = CollectionFactory.createStringMap();

    private ResourceSet automaticResources;

    /*
     * TODO The callback is meant to check whether the graph is initialized (and
     * not disposed) when methods are called (to prevent errors in asynchronous
     * callbacks that return after the graph has been disposed or before it has
     * been initialized).
     */
    private GraphNodeExpansionCallback expansionCallback = new GraphNodeExpansionCallback() {

        @Override
        public void addAutomaticResource(Resource resource) {
            Graph.this.addAutomaticResource(resource);
        }

        @Override
        public boolean containsResourceWithUri(String resourceUri) {
            return Graph.this.containsResourceWithUri(resourceUri);
        }

        @Override
        public String getCategory(Resource resource) {
            return Graph.this.getCategory(resource);
        }

        @Override
        public GraphDisplay getDisplay() {
            return Graph.this.getDisplay();
        }

        @Override
        public Resource getResourceByUri(String value) {
            return Graph.this.getResourceByUri(value);
        }

        @Override
        public ResourceManager getResourceManager() {
            return Graph.this.getResourceManager();
        }

        @Override
        public LightweightCollection<VisualItem> getVisualItems(Iterable<Resource> resources) {
            return Graph.this.getVisualItems(resources);
        }

        @Override
        public boolean isInitialized() {
            return Graph.this.isInitialized();
        }

        @Override
        public boolean isRestoring() {
            return Graph.this.isRestoring();
        }

        @Override
        public void updateArcsForResources(Iterable<Resource> resources) {
            Graph.this.updateArcsForResources(resources);
        }

        @Override
        public void updateArcsForVisuaItems(LightweightCollection<VisualItem> visualItems) {
            Graph.this.updateArcsForVisuaItems(visualItems);
        }
    };

    @Inject
    public Graph(GraphDisplay display, CommandManager commandManager, ResourceManager resourceManager,
            ResourceCategorizer resourceCategorizer, ArcTypeProvider arcStyleProvider,
            GraphExpansionRegistry registry) {

        assert display != null;
        assert commandManager != null;
        assert resourceManager != null;
        assert resourceCategorizer != null;
        assert arcStyleProvider != null;
        assert registry != null;

        this.arcStyleProvider = arcStyleProvider;
        this.resourceCategorizer = resourceCategorizer;
        graphDisplay = display;
        this.commandManager = commandManager;
        this.resourceManager = resourceManager;
        this.registry = registry;

        /*
         * we init the arc type containers early so they are available for UI
         * customization in Choosel applications.
         */
        initArcTypeContainers();
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T adaptTo(Class<T> clazz) throws NoSuchAdapterException {
        if (GraphLayoutSupport.class.equals(clazz)) {
            return (T) this;
        }

        return super.adaptTo(clazz);
    }

    private void addAutomaticResource(Resource resource) {
        automaticResources.add(resource);
    }

    private boolean containsResourceWithUri(String resourceUri) {
        return nodeResources.containsResourceWithUri(resourceUri);
    }

    private NodeItem createGraphNodeItem(VisualItem visualItem) {
        // TODO get from group id
        String type = getCategory(visualItem.getResources().getFirstElement());

        NodeItem graphItem = new NodeItem(visualItem, type, graphDisplay);

        graphDisplay.addNode(graphItem.getNode());
        positionNode(graphItem.getNode());

        // TODO re-enable
        // TODO remove once new drag and drop mechanism works...
        graphDisplay.setNodeStyle(graphItem.getNode(), "showDragImage", "false");

        graphDisplay.setNodeStyle(graphItem.getNode(), "showArrow",
                registry.getNodeMenuEntries(type).isEmpty() ? "false" : "true");

        nodeResources.addResourceSet(visualItem.getResources());
        visualItem.setDisplayObject(graphItem);

        /*
         * NOTE: all node configuration should be done when calling the
         * automatic expanders, since they rely on returning the correct graph
         * contents etc.
         * 
         * NOTE: we do not execute the expanders if we are restoring the graph
         */
        if (ready) {
            registry.getAutomaticExpander(type).expand(visualItem, expansionCallback);
        }

        return graphItem;
    }

    // TODO encapsulate in display, use dependency injection
    @Override
    protected Widget createWidget() {
        return graphDisplay.asWidget();
    }

    // TODO better caching?
    // default visibility for test case use
    List<Node> getAllNodes() {
        List<Node> result = new ArrayList<Node>();
        for (VisualItem visualItem : getVisualItems()) {
            result.add(getNode(visualItem));
        }
        return result;
    }

    public ResourceSet getAllResources() {
        return nodeResources;
    }

    public ArcItemContainer getArcItemContainer(String arcTypeID) {
        assert arcTypeID != null;
        assert arcItemContainersByArcTypeID.containsKey(arcTypeID);

        return arcItemContainersByArcTypeID.get(arcTypeID);
    }

    public Iterable<ArcItemContainer> getArcItemContainers() {
        return arcItemContainersByArcTypeID.values();
    }

    private ArcItem[] getArcItems() {
        Iterable<ArcItemContainer> arcItemContainers = getArcItemContainers();
        int size = 0;
        for (ArcItemContainer arcItemContainer : arcItemContainers) {
            size += arcItemContainer.getArcItems().size();
        }
        ArcItem[] arcs = new ArcItem[size];
        int i = 0;
        for (ArcItemContainer arcItemContainer : arcItemContainers) {
            for (ArcItem arcItem : arcItemContainer.getArcItems()) {
                arcs[i++] = arcItem;
            }
        }
        return arcs;
    }

    private String getCategory(Resource resource) {
        return resourceCategorizer.getCategory(resource);
    }

    private GraphDisplay getDisplay() {
        return graphDisplay;
    }

    @Override
    public Size getDisplayArea() {
        Widget displayWidget = graphDisplay.asWidget();
        int height = displayWidget.getOffsetHeight();
        int width = displayWidget.getOffsetWidth();
        return new DefaultSize(width, height);
    }

    @Override
    public String getName() {
        return "Graph";
    }

    private Node getNode(VisualItem visualItem) {
        return visualItem.<NodeItem>getDisplayObject().getNode();
    }

    private NodeItem[] getNodeItems() {
        LightweightCollection<VisualItem> visualItems = getVisualItems();
        NodeItem[] nodeItems = new NodeItem[visualItems.size()];
        int i = 0;
        for (VisualItem visualItem : visualItems) {
            nodeItems[i++] = visualItem.getDisplayObject();
        }
        return nodeItems;
    }

    public Resource getResourceByUri(String value) {
        return nodeResources.getByUri(value);
    }

    public ResourceManager getResourceManager() {
        return resourceManager;
    }

    // TODO cleanup
    @Override
    public SidePanelSection[] getSidePanelSections() {
        List<ViewContentDisplayAction> actions = new ArrayList<ViewContentDisplayAction>();

        actions.add(new GraphLayoutAction(GraphLayouts.CIRCLE_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.HORIZONTAL_TREE_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.VERTICAL_TREE_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.RADIAL_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.SPRING_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.INDENTED_TREE_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.GRID_LAYOUT_BY_NODE_ID));
        actions.add(new GraphLayoutAction(GraphLayouts.GRID_LAYOUT_BY_NODE_TYPE));
        actions.add(new GraphLayoutAction(GraphLayouts.GRID_LAYOUT_ALPHABETICAL));
        actions.add(new GraphLayoutAction(GraphLayouts.GRID_LAYOUT_BY_ARC_COUNT));
        actions.add(new GraphLayoutAction(GraphLayouts.HORIZONTAL_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.VERTICAL_LAYOUT));
        actions.add(new GraphLayoutAction(GraphLayouts.FORCE_DIRECTED_LAYOUT));

        VerticalPanel layoutPanel = new VerticalPanel();
        for (final ViewContentDisplayAction action : actions) {
            Button w = new Button(action.getLabel());
            w.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    action.execute();
                }
            });
            layoutPanel.add(w);
        }

        return new SidePanelSection[] { new SidePanelSection("Layouts", layoutPanel), };
    }

    @Override
    public Slot[] getSlots() {
        return new Slot[] { NODE_LABEL_SLOT, NODE_BORDER_COLOR, NODE_BACKGROUND_COLOR };
    }

    private VisualItem getVisualItem(Node node) {
        return getVisualItem(node.getId());
    }

    private VisualItem getVisualItem(NodeEvent<?> event) {
        return getVisualItem(event.getNode());
    }

    @Override
    public void init(VisualItemContainer container, ViewContentDisplayCallback callback) {

        super.init(container, callback);

        initStateChangeHandlers();
    }

    private void initArcTypeContainers() {
        for (ArcType arcType : arcStyleProvider.getArcTypes()) {
            arcItemContainersByArcTypeID.put(arcType.getArcTypeID(),
                    new ArcItemContainer(arcType, graphDisplay, this));
        }
    }

    private void initNodeMenuItems() {
        for (Entry<String, List<NodeMenuEntry>> entry : registry.getNodeMenuEntriesByCategory()) {

            String category = entry.getKey();
            for (NodeMenuEntry nodeMenuEntry : entry.getValue()) {
                registerNodeMenuItem(category, nodeMenuEntry.getLabel(), nodeMenuEntry.getExpander());
            }
        }
    }

    private void initStateChangeHandlers() {
        graphDisplay.addGraphDisplayReadyHandler(new GraphDisplayReadyEventHandler() {
            @Override
            public void onWidgetReady(GraphDisplayReadyEvent event) {
                ready = true;

                GraphEventHandler handler = new GraphEventHandler();

                graphDisplay.addEventHandler(NodeDragHandleMouseDownEvent.TYPE, handler);
                graphDisplay.addEventHandler(NodeMouseOverEvent.TYPE, handler);
                graphDisplay.addEventHandler(NodeMouseOutEvent.TYPE, handler);
                graphDisplay.addEventHandler(NodeMouseClickEvent.TYPE, handler);
                graphDisplay.addEventHandler(NodeDragEvent.TYPE, handler);
                graphDisplay.addEventHandler(MouseMoveEvent.getType(), handler);

                initNodeMenuItems();
            }
        });
        graphDisplay.addGraphDisplayLoadingFailureHandler(new GraphDisplayLoadingFailureEventHandler() {
            @Override
            public void onLoadingFailure(GraphDisplayLoadingFailureEvent event) {
                // TODO handle loading failures
            }
        });
    }

    @Override
    public boolean isAdaptableTo(Class<?> clazz) {
        if (GraphLayoutSupport.class.equals(clazz)) {
            return true;
        }

        return super.isAdaptableTo(clazz);
    }

    @Override
    public boolean isReady() {
        return ready;
    }

    private void positionNode(Node node) {
        // FIXME positioning: FlexVis takes care of positioning nodes into empty
        // space except for first node - if the node is the first node, we put
        // it in the center
        // TODO improve interface to access all resources

        assert node != null;

        if (getVisualItems().size() > 1) {
            return;
        }

        Widget displayWidget = graphDisplay.asWidget();
        if (displayWidget == null) {
            return; // for tests
        }

        int height = displayWidget.getOffsetHeight();
        int width = displayWidget.getOffsetWidth();

        graphDisplay.setLocation(node, new Point(width / 2, height / 2));
    }

    private void registerNodeMenuItem(String category, String menuLabel, final GraphNodeExpander nodeExpander) {

        graphDisplay.addNodeMenuItemHandler(menuLabel, new NodeMenuItemClickedHandler() {
            @Override
            public void onNodeMenuItemClicked(Node node) {
                if (!ready) {
                    return;
                }

                nodeExpander.expand(getVisualItem(node), expansionCallback);
            }
        }, category);
    }

    private void removeGraphNode(VisualItem visualItem) {
        assert visualItem != null;

        nodeResources.removeResourceSet(visualItem.getResources());
        for (ArcItemContainer arcItemContainer : arcItemContainersByArcTypeID.values()) {
            arcItemContainer.removeVisualItem(visualItem);
        }
        graphDisplay.removeNode(getNode(visualItem));
    }

    @Override
    public void restore(Memento state, PersistableRestorationService restorationService,
            ResourceSetAccessor accessor) {

        restoreArcItemContainers(restorationService, accessor, state.getChild(MEMENTO_ARC_ITEM_CONTAINERS_CHILD));
        restoreNodeLocations(state.getChild(MEMENTO_NODE_LOCATIONS_CHILD));
    }

    private void restoreArcItemContainers(PersistableRestorationService restorationService,
            ResourceSetAccessor accessor, Memento child) {
        for (Entry<String, Memento> entry : child.getChildren().entrySet()) {
            arcItemContainersByArcTypeID.get(entry.getKey()).restore(entry.getValue(), restorationService,
                    accessor);
        }
    }

    private void restoreNodeLocations(Memento state) {
        for (VisualItem visualItem : getVisualItems()) {
            NodeItem item = visualItem.getDisplayObject();
            Memento nodeMemento = state.getChild(visualItem.getId());
            Point location = new Point((Integer) nodeMemento.getValue(MEMENTO_X),
                    (Integer) nodeMemento.getValue(MEMENTO_Y));

            setLocation(item, location);
        }
    }

    @Override
    public void runLayout(GraphLayout layout) {
        assert layout != null;
        layout.run(getNodeItems(), getArcItems(), this);
    }

    @Override
    public void runLayout(String layout) {
        assert layout != null;
        graphDisplay.runLayout(layout);
    }

    @Override
    public Memento save(ResourceSetCollector resourceSetCollector) {
        Memento result = new Memento();

        result.addChild(MEMENTO_NODE_LOCATIONS_CHILD, saveNodeLocations());
        result.addChild(MEMENTO_ARC_ITEM_CONTAINERS_CHILD, saveArcTypeContainers(resourceSetCollector));

        return result;
    }

    private Memento saveArcTypeContainers(ResourceSetCollector resourceSetCollector) {

        Memento memento = new Memento();
        for (Entry<String, ArcItemContainer> entry : arcItemContainersByArcTypeID.entrySet()) {
            memento.addChild(entry.getKey(), entry.getValue().save(resourceSetCollector));
        }
        return memento;
    }

    private Memento saveNodeLocations() {
        Memento state = new Memento();

        for (VisualItem visualItem : getVisualItems()) {
            NodeItem nodeItem = visualItem.getDisplayObject();

            Point location = graphDisplay.getLocation(nodeItem.getNode());

            Memento nodeMemento = new Memento();
            nodeMemento.setValue(MEMENTO_X, location.getX());
            nodeMemento.setValue(MEMENTO_Y, location.getY());

            state.addChild(visualItem.getId(), nodeMemento);
        }
        return state;
    }

    /**
     * If the arc type becomes invisible, all arcs of this arcType from the view
     * and arcs of this arc type are not shown any more. If the arc types
     * becomes visible, all arcs of this type are added.
     */
    // TODO expose arc type configurations and use listener mechanism
    public void setArcTypeVisible(String arcTypeId, boolean visible) {
        assert arcTypeId != null;
        assert arcItemContainersByArcTypeID.containsKey(arcTypeId);

        arcItemContainersByArcTypeID.get(arcTypeId).setVisible(visible);
    }

    @Override
    public void setAutomaticResources(ResourceSet automaticResources) {
        this.automaticResources = automaticResources;
    }

    @Override
    public void setLocation(NodeItem node, Point location) {
        graphDisplay.setLocation(node.getNode(), location);
    }

    @Override
    public void update(Delta<VisualItem> delta, LightweightCollection<Slot> updatedSlots) {

        for (VisualItem addedItem : delta.getAddedElements()) {
            createGraphNodeItem(addedItem);
            updateNode(addedItem);
        }

        updateArcsForVisuaItems(delta.getAddedElements());

        for (VisualItem updatedItem : delta.getUpdatedElements()) {
            updateNode(updatedItem);
        }

        for (VisualItem visualItem : delta.getRemovedElements()) {
            removeGraphNode(visualItem);
        }

        if (!updatedSlots.isEmpty()) {
            for (VisualItem visualItem : getVisualItems()) {
                updateNode(visualItem);
            }
        }
    }

    public void updateArcsForResources(Iterable<Resource> resources) {
        updateArcsForVisuaItems(getVisualItems(resources));
    }

    public void updateArcsForVisuaItems(LightweightCollection<VisualItem> visualItems) {
        assert visualItems != null;
        for (ArcItemContainer container : arcItemContainersByArcTypeID.values()) {
            container.update(visualItems);
        }
    }

    private void updateNode(VisualItem visualItem) {
        visualItem.<NodeItem>getDisplayObject().updateNode();
    }

}