/*******************************************************************************
* Copyright (c) 2013, 2016 Fabian Steeg and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Fabian Steeg - initial API and implementation (bug #372365)
* Alexander Nyßen - refactoring of builder API (bug #480293)
*
*******************************************************************************/
package org.eclipse.gef.graph;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.UUID;
import java.util.function.BiConsumer;
import org.eclipse.gef.common.attributes.IAttributeStore;
import org.eclipse.gef.common.beans.property.ReadOnlyListWrapperEx;
import org.eclipse.gef.common.beans.property.ReadOnlyMapWrapperEx;
import org.eclipse.gef.common.collections.CollectionUtils;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.ReadOnlyMapProperty;
import javafx.beans.property.ReadOnlyMapWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
/**
* A {@link Graph} is a container for {@link Node}s and {@link Edge}s between
* those {@link Node}s.
*
* @author Fabian Steeg
* @author anyssen
*
*/
public final class Graph implements IAttributeStore {
/**
* The {@link Builder} can be used to construct a new {@link Graph} little
* by little.
*/
public static class Builder {
/**
* A context object passed to nested builders when creating a builder
* chain.
*
*/
protected class Context {
/**
* The {@link Graph.Builder} used to construct the {@link Graph},
* i.e. the root of the builder chain.
*/
protected Graph.Builder builder;
/**
* {@link Node.Builder}s, which are part of the builder chain,
* mapped to their keys.
*/
protected Map<Object, Node.Builder> nodeBuilders = new HashMap<>();
/**
* {@link Edge.Builder}s, which are part of the builder chain.
*/
protected List<Edge.Builder> edgeBuilders = new ArrayList<>();
/**
* Stores incoming node keys in order.
*/
protected List<Object> nodeKeys = new ArrayList<>();
/**
* Stores node builders
*/
protected Map<Object, Node.Builder> nodeKeyToBuilderMap = new HashMap<>();
}
private List<Entry<Object, Object>> attr = new ArrayList<>();
private HashMap<Object, Node> nodes = new HashMap<>();
private List<Edge> edges = new ArrayList<>();
private Context context;
/**
* Constructs a new {@link Builder} without {@link Node}s and
* {@link Edge}s.
*/
public Builder() {
context = new Context();
context.builder = this;
}
/**
* Uses the given setter to set the attribute value.
*
* @param <T>
* The type of the attribute.
*
* @param setter
* The setter to apply.
* @param value
* The value to apply.
* @return <code>this</code> for convenience.
*/
public <T> Graph.Builder attr(BiConsumer<Graph, T> setter, T value) {
attr.add(new AbstractMap.SimpleImmutableEntry<>(setter, value));
return this;
}
/**
* Puts the given <i>key</i>-<i>value</i>-pair into the
* {@link Graph#attributesProperty() attributes map} of the
* {@link Graph} which is constructed by this {@link Builder}.
*
* @param key
* The attribute name which is inserted.
* @param value
* The attribute value which is inserted.
* @return <code>this</code> for convenience.
*/
public Graph.Builder attr(String key, Object value) {
attr.add(new AbstractMap.SimpleImmutableEntry<>(key, value));
return this;
}
/**
* Constructs a new {@link Graph} from the values which have been
* supplied to this {@link Builder}.
*
* @return A new {@link Graph} from the values which have been supplied
* to this {@link Builder}.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Graph build() {
for (Node.Builder nb : context.nodeBuilders.values()) {
nodes.put(nb.getKey(), nb.buildNode());
}
for (Edge.Builder eb : context.edgeBuilders) {
edges.add(eb.buildEdge());
}
List<Node> nodeList = new ArrayList<>();
for (Object key : context.nodeKeys) {
nodeList.add(nodes.get(key));
}
Graph g = new Graph(nodeList, edges);
for (Entry<Object, Object> s : attr) {
if (s.getKey() instanceof String) {
g.attributesProperty().put((String) s.getKey(), s.getValue());
} else {
((BiConsumer) s.getKey()).accept(g, s.getValue());
}
}
return g;
}
/**
* Constructs a new {@link Edge.Builder}.
*
* @param sourceNodeOrKey
* The source {@link Node} or a key to identify the source
* {@link Node} (or its {@link Node.Builder}).
*
* @param targetNodeOrKey
* The target {@link Node} or a key to identify the target
* {@link Node} (or its {@link Node.Builder}).
*
* @return A new {@link Edge.Builder}.
*/
public Edge.Builder edge(Object sourceNodeOrKey, Object targetNodeOrKey) {
Edge.Builder eb = new Edge.Builder(context, sourceNodeOrKey, targetNodeOrKey);
return eb;
}
/**
* Adds the given {@link Edge}s to the {@link Graph} which is
* constructed by this {@link Builder}.
*
* @param edges
* The {@link Edge}s which are added to the {@link Graph}
* which is constructed by this {@link Builder}.
* @return <code>this</code> for convenience.
*/
public Graph.Builder edges(Collection<Edge> edges) {
return edges(edges.toArray(new Edge[] {}));
}
/**
* Adds the given {@link Edge}s to the {@link Graph} which is
* constructed by this {@link Builder}.
*
* @param edges
* The {@link Edge}s which are added to the {@link Graph}
* which is constructed by this {@link Builder}.
* @return <code>this</code> for convenience.
*/
public Graph.Builder edges(Edge... edges) {
this.edges.addAll(Arrays.asList(edges));
return this;
}
/**
* Retrieves the node already created by a builder for the given key, or
* creates a new one via the respective {@link Node.Builder}.
*
* @param key
* The key to identify the {@link Node} or
* {@link Node.Builder}.
* @return An existing or newly created {@link Node}.
*/
protected Node findOrCreateNode(Object key) {
// if we have already created a new with the given key, return the
// created node
if (nodes.containsKey(key)) {
return nodes.get(key);
} else {
// create a new node
org.eclipse.gef.graph.Node.Builder nodeBuilder = context.nodeBuilders.get(key);
if (nodeBuilder == null) {
return null;
}
context.nodeKeys.add(key);
nodes.put(key, nodeBuilder.buildNode());
}
return nodes.get(key);
}
/**
* Constructs a new (anonymous) {@link Node.Builder}.
*
* @return A new {@link Node.Builder}.
*/
public Node.Builder node() {
Node.Builder nb = new Node.Builder(context);
context.nodeKeys.add(nb.getKey());
return nb;
}
/**
* Constructs a new (identifiable) {@link Node.Builder}.
*
* @param key
* The key that can be used to identify the
* {@link Node.Builder}
*
* @return A new {@link Node.Builder}.
*/
public Node.Builder node(Object key) {
if (context.nodeBuilders.containsKey(key)) {
return context.nodeBuilders.get(key);
}
Node.Builder nb = new Node.Builder(context, key);
context.nodeKeys.add(key);
return nb;
}
/**
* Adds the given {@link Node}s to the {@link Graph} which is
* constructed by this {@link Builder}.
*
* @param nodes
* The {@link Node}s which are added to the {@link Graph}
* which is constructed by this {@link Builder}.
* @return <code>this</code> for convenience.
*/
public Graph.Builder nodes(Collection<Node> nodes) {
return nodes(nodes.toArray(new Node[] {}));
}
/**
* Adds the given {@link Node}s to the {@link Graph} which is
* constructed by this {@link Builder}.
*
* @param nodes
* The {@link Node}s which are added to the {@link Graph}
* which is constructed by this {@link Builder}.
* @return <code>this</code> for convenience.
*/
public Graph.Builder nodes(Node... nodes) {
for (Node n : nodes) {
// use a unique id for each given node (they are not
// identifiable from outside, so we just have to ensure the key
// is not already used)
UUID key = UUID.randomUUID();
context.nodeKeys.add(key);
this.nodes.put(key, n);
}
return this;
}
}
/**
* The name of the {@link #getNodes() nodes property}.
*/
public static final String NODES_PROPERTY = "nodes";
/**
* The name of the {@link #getEdges() edgesProperty property}.
*/
public static final String EDGES_PROPERTY = "edgesProperty";
/**
* {@link Node}s directly contained by this {@link Graph}.
*/
private final ReadOnlyListWrapper<Node> nodesProperty = new ReadOnlyListWrapperEx<>(this, NODES_PROPERTY,
CollectionUtils.<Node>observableArrayList());
/**
* {@link Edge}s for which this {@link Graph} is a common ancestor for
* {@link Edge#getSource() source} and {@link Edge#getTarget() target}.
*/
private final ReadOnlyListWrapper<Edge> edgesProperty = new ReadOnlyListWrapperEx<>(this, EDGES_PROPERTY,
CollectionUtils.<Edge>observableArrayList());
/**
* Attributes of this {@link Graph}.
*/
private final ReadOnlyMapWrapper<String, Object> attributesProperty = new ReadOnlyMapWrapperEx<>(this,
ATTRIBUTES_PROPERTY, FXCollections.<String, Object>observableHashMap());
/**
* {@link Node} which contains this {@link Graph}. May be <code>null</code>
* .
*/
private Node nestingNode; // when contained as a nested graph within a node
/**
* Default constructor, using empty collections for attributes, nodes, and
* edges.
*/
public Graph() {
this(new HashMap<String, Object>(), new ArrayList<Node>(), new ArrayList<Edge>());
}
/**
* Constructs a new {@link Graph} from the given nodes, and edges but empty
* attributes. Associates all nodes and edgesProperty with this
* {@link Graph}.
*
* @param nodes
* List of {@link Node}s.
* @param edges
* List of {@link Edge}s.
*/
public Graph(Collection<? extends Node> nodes, Collection<? extends Edge> edges) {
this(new HashMap<String, Object>(), nodes, edges);
}
/**
* Constructs a new {@link Graph} from the given attributes, nodes, and
* edgesProperty. Associates all nodes and edgesProperty with this
* {@link Graph}.
*
* @param attributes
* Map of graph attributes.
* @param nodes
* List of {@link Node}s.
* @param edges
* List of {@link Edge}s.
*/
public Graph(Map<String, Object> attributes, Collection<? extends Node> nodes, Collection<? extends Edge> edges) {
this.attributesProperty.putAll(attributes);
this.nodesProperty.addListener(new ListChangeListener<Node>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Node> c) {
while (c.next()) {
for (Node n : c.getAddedSubList()) {
n.setGraph(Graph.this);
}
for (Node n : c.getRemoved()) {
n.setGraph(null);
}
}
}
});
this.edgesProperty.addListener(new ListChangeListener<Edge>() {
@Override
public void onChanged(ListChangeListener.Change<? extends Edge> c) {
while (c.next()) {
for (Edge e : c.getAddedSubList()) {
e.setGraph(Graph.this);
}
for (Edge e : c.getRemoved()) {
e.setGraph(null);
}
}
}
});
this.nodesProperty.addAll(nodes);
this.edgesProperty.addAll(edges);
}
@Override
public ReadOnlyMapProperty<String, Object> attributesProperty() {
return attributesProperty.getReadOnlyProperty();
}
/**
* Returns a read-only list property containing the {@link Edge}s of this
* {@link Graph}.
*
* @return The list of {@link Edge}s of this {@link Graph}.
*/
public ReadOnlyListProperty<Edge> edgesProperty() {
return edgesProperty.getReadOnlyProperty();
}
@Override
public ObservableMap<String, Object> getAttributes() {
return attributesProperty.get();
}
/**
* Returns the edgesProperty of this {@link Graph}.
*
* @return A list containing the edgesProperty.
*/
public ObservableList<Edge> getEdges() {
return edgesProperty.getReadOnlyProperty();
}
/**
* Returns the {@link Node} in which this {@link Graph} is nested. Returns
* <code>null</code> when this {@link Graph} is not nested.
*
* @return The {@link Node} in which this {@link Graph} is nested, or
* <code>null</code>.
*/
public Node getNestingNode() {
return nestingNode;
}
/**
* Returns the nodes of this Graph.
*
* @return A list containing the nodes.
*/
public ObservableList<Node> getNodes() {
return nodesProperty.getReadOnlyProperty();
}
/**
* Returns the root graph of this Graph.
*
* @return the root graph of this graph.
*/
public Graph getRootGraph() {
if (nestingNode == null) {
return this;
} else {
return nestingNode.getGraph().getRootGraph();
}
}
/**
* Returns a read-only list property containing the {@link Node}s of this
* {@link Graph}.
*
* @return A read-only list property.
*/
public ReadOnlyListProperty<Node> nodesProperty() {
return nodesProperty.getReadOnlyProperty();
}
/**
* Sets the nesting {@link Node} of this {@link Graph}.
*
* @param nestingNode
* The new {@link Node} in which this {@link Graph} is nested.
*/
public void setNestingNode(Node nestingNode) {
Node oldNestingNode = this.nestingNode;
this.nestingNode = nestingNode;
if (oldNestingNode != null && oldNestingNode != nestingNode) {
oldNestingNode.setNestedGraph(null);
}
if (nestingNode != null && oldNestingNode != nestingNode) {
nestingNode.setNestedGraph(this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Graph");
sb.append(" attr {");
boolean separator = false;
TreeMap<String, Object> sortedAttrs = new TreeMap<>();
sortedAttrs.putAll(attributesProperty);
for (Object attrKey : sortedAttrs.keySet()) {
if (separator) {
sb.append(", ");
} else {
separator = true;
}
sb.append(attrKey.toString() + " : " + attributesProperty.get(attrKey));
}
sb.append("}");
sb.append(".nodes {");
separator = false;
for (Node n : getNodes()) {
if (separator) {
sb.append(", ");
} else {
separator = true;
}
sb.append(n.toString());
}
sb.append("}");
sb.append(".edges {");
separator = false;
for (Edge e : getEdges()) {
if (separator) {
sb.append(", ");
} else {
separator = true;
}
sb.append(e.toString());
}
sb.append("}");
return sb.toString();
}
}