All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.graph.implementations.AbstractGraph Maven / Gradle / Ivy

/*
 * This file is part of GraphStream .
 *
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 *
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL  or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 *
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */

/**
 * @author Stefan Balev 
 * @author Richard O. Legendi 
 * @author Guilhelm Savin 
 * @author Yoann Pigné 
 * @author Antoine Dutot 
 * @author Alex Bowen 
 * @author Hicham Brahimi 
 * @since 2011-07-22
 */
package org.graphstream.graph.implementations;

import java.util.Collection;
import java.util.Iterator;
import java.util.stream.Collectors;

import org.graphstream.graph.Edge;
import org.graphstream.graph.EdgeFactory;
import org.graphstream.graph.EdgeRejectedException;
import org.graphstream.graph.ElementNotFoundException;
import org.graphstream.graph.Graph;
import org.graphstream.graph.IdAlreadyInUseException;
import org.graphstream.graph.Node;
import org.graphstream.graph.NodeFactory;
import org.graphstream.stream.AttributeSink;
import org.graphstream.stream.ElementSink;
import org.graphstream.stream.Replayable;
import org.graphstream.stream.Sink;
import org.graphstream.stream.SourceBase;
import org.graphstream.ui.view.Viewer;
import org.graphstream.util.Display;
import org.graphstream.util.GraphListeners;
import org.graphstream.util.MissingDisplayException;

/**
 * 

* This class provides a basic implementation of * {@link org.graphstream.graph.Graph} interface, to minimize the effort * required to implement this interface. It provides event management * implementing all the methods of {@link org.graphstream.stream.Pipe}. It also * manages strict checking and auto-creation policies, as well as other services * as displaying, reading and writing. *

*

*

* Subclasses have to maintain data structures allowing to efficiently access * graph elements by their id or index and iterating on them. They also have to * maintain coherent indices of the graph elements. When AbstractGraph decides * to add or remove elements, it calls one of the "callbacks" * {@link #addNodeCallback(AbstractNode)}, * {@link #addEdgeCallback(AbstractEdge)}, * {@link #removeNodeCallback(AbstractNode)}, * {@link #removeEdgeCallback(AbstractEdge)}, {@link #clearCallback()}. The role * of these callbacks is to update the data structures and to re-index elements * if necessary. *

*/ public abstract class AbstractGraph extends AbstractElement implements Graph, Replayable { // *** Fields *** GraphListeners listeners; private boolean strictChecking; private boolean autoCreate; private NodeFactory nodeFactory; private EdgeFactory edgeFactory; private double step = 0; private long replayId = 0; // *** Constructors *** /** * The same as {@code AbstractGraph(id, true, false)} * * @param id * Identifier of the graph * @see #AbstractGraph(String, boolean, boolean) */ public AbstractGraph(String id) { this(id, true, false); } /** * Creates a new graph. Subclasses must create their node and edge factories and * initialize their data structures in their constructors. * * @param id * @param strictChecking * @param autoCreate */ public AbstractGraph(String id, boolean strictChecking, boolean autoCreate) { super(id); this.strictChecking = strictChecking; this.autoCreate = autoCreate; this.listeners = new GraphListeners(this); } // *** Inherited from abstract element @Override protected void attributeChanged(AttributeChangeEvent event, String attribute, Object oldValue, Object newValue) { listeners.sendAttributeChangedEvent(id, SourceBase.ElementType.GRAPH, attribute, event, oldValue, newValue); } // *** Inherited from graph *** /** * This implementation returns an iterator over nodes. * * @see java.lang.Iterable#iterator() */ @Override public Iterator iterator() { return nodes().iterator(); } // Factories @Override public NodeFactory nodeFactory() { return nodeFactory; } @Override public EdgeFactory edgeFactory() { return edgeFactory; } @Override @SuppressWarnings("unchecked") public void setNodeFactory(NodeFactory nf) { nodeFactory = (NodeFactory) nf; } @Override @SuppressWarnings("unchecked") public void setEdgeFactory(EdgeFactory ef) { edgeFactory = (EdgeFactory) ef; } // strict checking, autocreation, etc @Override public boolean isStrict() { return strictChecking; } @Override public void setStrict(boolean on) { strictChecking = on; } @Override public boolean isAutoCreationEnabled() { return autoCreate; } @Override public double getStep() { return step; } @Override public void setAutoCreate(boolean on) { autoCreate = on; } @Override public void stepBegins(double time) { listeners.sendStepBegins(time); this.step = time; } // display, read, write public Viewer display() { return display(true); } public Viewer display(boolean autoLayout) { try { Display display = Display.getDefault(); return display.display(this, autoLayout); } catch (MissingDisplayException e) { throw new RuntimeException("Cannot launch viewer.", e); } } /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#clear() */ @Override public void clear() { listeners.sendGraphCleared(); nodes().forEach(n -> ((AbstractNode) n).clearCallback()); clearCallback(); clearAttributesWithNoEvent(); } /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#addNode(java.lang.String) */ @Override public Node addNode(String id) { AbstractNode node = (AbstractNode) getNode(id); if (node != null) { if (strictChecking) throw new IdAlreadyInUseException("id \"" + id + "\" already in use. Cannot create a node."); return node; } node = nodeFactory.newInstance(id, this); addNodeCallback(node); listeners.sendNodeAdded(id); return node; } /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#addEdge(java.lang.String, * org.graphstream.graph.Node, org.graphstream.graph.Node, boolean) */ @Override public Edge addEdge(String id, Node from, Node to, boolean directed) { return addEdge(id, (AbstractNode) from, from.getId(), (AbstractNode) to, to.getId(), directed); } /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#removeNode(org.graphstream.graph.Node) */ @Override public Node removeNode(Node node) { if (node == null) return null; removeNode((AbstractNode) node, true); return node; } /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#removeEdge(org.graphstream.graph.Edge) */ @Override public Edge removeEdge(Edge edge) { if (edge == null) return null; removeEdge((AbstractEdge) edge, true, true, true); return edge; } /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#removeEdge(org.graphstream.graph.Node, * org.graphstream.graph.Node) */ @Override public Edge removeEdge(Node node1, Node node2) { Edge edge = node1.getEdgeToward(node2); if (edge == null) { if (strictChecking) throw new ElementNotFoundException("There is no edge from \"%s\" to \"%s\". Cannot remove it.", node1.getId(), node2.getId()); return null; } return removeEdge(edge); } // *** Sinks, sources etc. *** /* * (non-Javadoc) * * @see org.graphstream.graph.Graph#attributeSinks() */ @Override public Iterable attributeSinks() { return listeners.attributeSinks(); } /* * *(non-Javadoc) * * @see org.graphstream.graph.Graph#elementSinks() */ @Override public Iterable elementSinks() { return listeners.elementSinks(); } /* * *(non-Javadoc) * * @see org.graphstream.stream.Source#addAttributeSink(org.graphstream.stream * .AttributeSink) */ @Override public void addAttributeSink(AttributeSink sink) { listeners.addAttributeSink(sink); } /* * (non-Javadoc) * * @see org.graphstream.stream.Source#addElementSink(org.graphstream.stream. * ElementSink) */ @Override public void addElementSink(ElementSink sink) { listeners.addElementSink(sink); } /* * (non-Javadoc) * * @see org.graphstream.stream.Source#addSink(org.graphstream.stream.Sink) */ @Override public void addSink(Sink sink) { listeners.addSink(sink); } /* * (non-Javadoc) * * @see org.graphstream.stream.Source#clearAttributeSinks() */ @Override public void clearAttributeSinks() { listeners.clearAttributeSinks(); } /* * *(non-Javadoc) * * @see org.graphstream.stream.Source#clearElementSinks() */ @Override public void clearElementSinks() { listeners.clearElementSinks(); } /* * *(non-Javadoc) * * @see org.graphstream.stream.Source#clearSinks() */ @Override public void clearSinks() { listeners.clearSinks(); } /* * (non-Javadoc) * * @see org.graphstream.stream.Source#removeAttributeSink(org.graphstream.stream * .AttributeSink) */ @Override public void removeAttributeSink(AttributeSink sink) { listeners.removeAttributeSink(sink); } /* * (non-Javadoc) * * @see org.graphstream.stream.Source#removeElementSink(org.graphstream.stream * .ElementSink) */ @Override public void removeElementSink(ElementSink sink) { listeners.removeElementSink(sink); } /* * (non-Javadoc) * * @see org.graphstream.stream.Source#removeSink(org.graphstream.stream.Sink) */ @Override public void removeSink(Sink sink) { listeners.removeSink(sink); } @Override public void edgeAttributeAdded(String sourceId, long timeId, String edgeId, String attribute, Object value) { listeners.edgeAttributeAdded(sourceId, timeId, edgeId, attribute, value); } @Override public void edgeAttributeChanged(String sourceId, long timeId, String edgeId, String attribute, Object oldValue, Object newValue) { listeners.edgeAttributeChanged(sourceId, timeId, edgeId, attribute, oldValue, newValue); } @Override public void edgeAttributeRemoved(String sourceId, long timeId, String edgeId, String attribute) { listeners.edgeAttributeRemoved(sourceId, timeId, edgeId, attribute); } @Override public void graphAttributeAdded(String sourceId, long timeId, String attribute, Object value) { listeners.graphAttributeAdded(sourceId, timeId, attribute, value); } @Override public void graphAttributeChanged(String sourceId, long timeId, String attribute, Object oldValue, Object newValue) { listeners.graphAttributeChanged(sourceId, timeId, attribute, oldValue, newValue); } @Override public void graphAttributeRemoved(String sourceId, long timeId, String attribute) { listeners.graphAttributeRemoved(sourceId, timeId, attribute); } @Override public void nodeAttributeAdded(String sourceId, long timeId, String nodeId, String attribute, Object value) { listeners.nodeAttributeAdded(sourceId, timeId, nodeId, attribute, value); } @Override public void nodeAttributeChanged(String sourceId, long timeId, String nodeId, String attribute, Object oldValue, Object newValue) { listeners.nodeAttributeChanged(sourceId, timeId, nodeId, attribute, oldValue, newValue); } @Override public void nodeAttributeRemoved(String sourceId, long timeId, String nodeId, String attribute) { listeners.nodeAttributeRemoved(sourceId, timeId, nodeId, attribute); } @Override public void edgeAdded(String sourceId, long timeId, String edgeId, String fromNodeId, String toNodeId, boolean directed) { listeners.edgeAdded(sourceId, timeId, edgeId, fromNodeId, toNodeId, directed); } @Override public void edgeRemoved(String sourceId, long timeId, String edgeId) { listeners.edgeRemoved(sourceId, timeId, edgeId); } @Override public void graphCleared(String sourceId, long timeId) { listeners.graphCleared(sourceId, timeId); } @Override public void nodeAdded(String sourceId, long timeId, String nodeId) { listeners.nodeAdded(sourceId, timeId, nodeId); } @Override public void nodeRemoved(String sourceId, long timeId, String nodeId) { listeners.nodeRemoved(sourceId, timeId, nodeId); } @Override public void stepBegins(String sourceId, long timeId, double step) { listeners.stepBegins(sourceId, timeId, step); } /* * (non-Javadoc) * * @see org.graphstream.stream.Replayable#getReplayController() */ @Override public Replayable.Controller getReplayController() { return new GraphReplayController(); } // *** callbacks maintaining user's data structure /** * This method is automatically called when a new node is created. Subclasses * must add the new node to their data structure and to set its index correctly. * * @param node * the node to be added */ protected abstract void addNodeCallback(AbstractNode node); /** * This method is automatically called when a new edge is created. Subclasses * must add the new edge to their data structure and to set its index correctly. * * @param edge * the edge to be added */ protected abstract void addEdgeCallback(AbstractEdge edge); /** * This method is automatically called when a node is removed. Subclasses must * remove the node from their data structures and to re-index other node(s) so * that node indices remain coherent. * * @param node * the node to be removed */ protected abstract void removeNodeCallback(AbstractNode node); /** * This method is automatically called when an edge is removed. Subclasses must * remove the edge from their data structures and re-index other edge(s) so that * edge indices remain coherent. * * @param edge * the edge to be removed */ protected abstract void removeEdgeCallback(AbstractEdge edge); /** * This method is automatically called when the graph is cleared. Subclasses * must remove all the nodes and all the edges from their data structures. */ protected abstract void clearCallback(); // *** _ methods *** // Why do we pass both the ids and the references of the endpoints here? // When the caller knows the references it's stupid to call getNode(id) // here. If the node does not exist the reference will be null. // And if autoCreate is on, we need also the id. Sad but true! protected Edge addEdge(String edgeId, AbstractNode src, String srcId, AbstractNode dst, String dstId, boolean directed) { AbstractEdge edge = (AbstractEdge) getEdge(edgeId); if (edge != null) { if (strictChecking) throw new IdAlreadyInUseException("id \"" + edgeId + "\" already in use. Cannot create an edge."); if ((edge.getSourceNode() == src && edge.getTargetNode() == dst) || (!directed && edge.getTargetNode() == src && edge.getSourceNode() == dst)) return edge; return null; } if (src == null || dst == null) { if (strictChecking) throw new ElementNotFoundException( String.format("Cannot create edge %s[%s-%s%s]. Node '%s' does not exist.", edgeId, srcId, directed ? ">" : "-", dstId, src == null ? srcId : dstId)); if (!autoCreate) return null; if (src == null) src = (AbstractNode) addNode(srcId); if (dst == null) dst = (AbstractNode) addNode(dstId); } // at this point edgeId is not in use and both src and dst are not null if(src.getGraph() != this || dst.getGraph() != this) { throw new ElementNotFoundException("At least one of two nodes does not belong to the graph."); } edge = edgeFactory.newInstance(edgeId, src, dst, directed); // see if the endpoints accept the edge if (!src.addEdgeCallback(edge)) { if (strictChecking) throw new EdgeRejectedException("Edge " + edge + " was rejected by node " + src); return null; } // note that for loop edges the callback is called only once if (src != dst && !dst.addEdgeCallback(edge)) { // the edge is accepted by src but rejected by dst // so we have to remove it from src src.removeEdgeCallback(edge); if (strictChecking) throw new EdgeRejectedException("Edge " + edge + " was rejected by node " + dst); return null; } // now we can finally add it addEdgeCallback(edge); listeners.sendEdgeAdded(edgeId, srcId, dstId, directed); return edge; } // helper for removeNode_ private void removeAllEdges(AbstractNode node) { Collection toRemove = node.edges().collect(Collectors.toList()); toRemove.forEach(this::removeEdge); } // *** Methods for iterators *** /** * This method is similar to {@link #removeNode(Node)} but allows to control if * {@link #removeNodeCallback(AbstractNode)} is called or not. It is useful for * iterators supporting {@link java.util.Iterator#remove()} who want to update * the data structures by their owns. * * @param node * the node to be removed * @param graphCallback * if {@code false}, {@code removeNodeCallback(node)} is not called */ protected void removeNode(AbstractNode node, boolean graphCallback) { if (node == null) { throw new NullPointerException("node reference is null"); } if (node.getGraph() != this){ throw new ElementNotFoundException( "Node \""+node.getId()+"\" does not belong to this graph"); } removeAllEdges(node); listeners.sendNodeRemoved(node.getId()); if (graphCallback) removeNodeCallback(node); } /** * This method is similar to {@link #removeEdge(Edge)} but allows to control if * different callbacks are called or not. It is useful for iterators supporting * {@link java.util.Iterator#remove()} who want to update the data structures by * their owns. * * @param edge * the edge to be removed * @param graphCallback * if {@code false}, {@link #removeEdgeCallback(AbstractEdge)} of the * graph is not called * @param sourceCallback * if {@code false}, * {@link AbstractNode#removeEdgeCallback(AbstractEdge)} is not * called for the source node of the edge * @param targetCallback * if {@code false}, * {@link AbstractNode#removeEdgeCallback(AbstractEdge)} is not * called for the target node of the edge */ protected void removeEdge(AbstractEdge edge, boolean graphCallback, boolean sourceCallback, boolean targetCallback) { if (edge == null) { throw new NullPointerException("edge reference is null"); } AbstractNode src = (AbstractNode) edge.getSourceNode(); AbstractNode dst = (AbstractNode) edge.getTargetNode(); if (src.getGraph() != this || dst.getGraph() != this){ throw new ElementNotFoundException( "Edge \""+edge.getId()+"\" does not belong to this graph"); } listeners.sendEdgeRemoved(edge.getId()); if (sourceCallback) src.removeEdgeCallback(edge); if (src != dst && targetCallback) dst.removeEdgeCallback(edge); if (graphCallback) removeEdgeCallback(edge); } class GraphReplayController extends SourceBase implements Replayable.Controller { GraphReplayController() { super(AbstractGraph.this.id + "replay"); } /* * (non-Javadoc) * * @see org.graphstream.stream.Replayable.Controller#replay() */ @Override public void replay() { String sourceId = String.format("%s-replay-%x", id, replayId++); replay(sourceId); } /* * (non-Javadoc) * * @see org.graphstream.stream.Replayable.Controller#replay(java.lang.String) */ @Override public void replay(String sourceId) { attributeKeys().forEach(key -> sendGraphAttributeAdded(sourceId, key, getAttribute(key))); for (int i = 0; i < getNodeCount(); i++) { Node node = getNode(i); String nodeId = node.getId(); sendNodeAdded(sourceId, nodeId); node.attributeKeys() .forEach(key -> sendNodeAttributeAdded(sourceId, nodeId, key, node.getAttribute(key))); } for (int i = 0; i < getEdgeCount(); i++) { Edge edge = getEdge(i); String edgeId = edge.getId(); sendEdgeAdded(sourceId, edgeId, edge.getNode0().getId(), edge.getNode1().getId(), edge.isDirected()); edge.attributeKeys() .forEach(key -> sendEdgeAttributeAdded(sourceId, edgeId, key, edge.getAttribute(key))); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy