/*******************************************************************************
* 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)
* Matthias Wienand (itemis AG) - contribution for bugs #438734, #461296
* Alexander Nyßen (itemis AG) - refactoring of builder API (bug #480293)
*******************************************************************************/
package org.eclipse.gef.graph;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.ReadOnlyMapWrapperEx;
import org.eclipse.gef.graph.Graph.Builder.Context;
import javafx.beans.property.ReadOnlyMapProperty;
import javafx.beans.property.ReadOnlyMapWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
/**
* A {@link Node} represents a vertex within a {@link Graph}.
*
* @author Fabian Steeg
* @author Alexander Nyßen
*
*/
public class Node implements IAttributeStore {
/**
* The {@link Builder} can be used to construct a {@link Node} little by
* little.
*/
public static class Builder {
private Graph.Builder.Context context;
private Object key;
private List<Entry<Object, Object>> attr = new ArrayList<>();
/**
* Constructs a new (anonymous) context-free {@link Node.Builder}, which
* can only be used to construct a single node via {@link #buildNode()},
* i.e. which cannot be chained.
*/
public Builder() {
this(null);
}
/**
* Constructs a new (anonymous) {@link Node.Builder} for the given
* {@link Context}.
*
* @param context
* The context in which the {@link Node.Builder} is used.
*/
public Builder(Graph.Builder.Context context) {
this(context, null);
}
/**
* Constructs a new identifiable {@link Node.Builder} for the given
* {@link Context}.
*
* @param context
* The context in which the {@link Node.Builder} is used.
* @param key
* The key to identify the builder.
*/
public Builder(Graph.Builder.Context context, Object key) {
this.context = context;
if (context != null) {
this.key = (key == null) ? UUID.randomUUID() : key;
this.context.nodeBuilders.put(this.key, 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> Node.Builder attr(BiConsumer<Node, 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 Node#attributesProperty() attributesProperty map} of the
* {@link Node} 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 Node.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 the builder chain.
*
* @return A new {@link Graph}.
*/
public Graph build() {
return context.builder.build();
}
/**
* Creates a new {@link Node}, setting the values specified via this
* {@link Node.Builder}.
*
* @return A newly created {@link Node}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Node buildNode() {
Node n = new Node();
for (Entry<Object, Object> s : attr) {
if (s.getKey() instanceof String) {
n.attributesProperty().put((String) s.getKey(), s.getValue());
} else {
((BiConsumer) s.getKey()).accept(n, s.getValue());
}
}
return n;
}
/**
* 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;
}
/**
* Returns the key that can be used to identify this
* {@link Node.Builder}
*
* @return The key that can be used for identification.
*/
protected Object getKey() {
return 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) {
Node.Builder nb = new Node.Builder(context, key);
context.nodeKeys.add(key);
return nb;
}
}
private final ReadOnlyMapWrapper<String, Object> attributesProperty = new ReadOnlyMapWrapperEx<>(this,
ATTRIBUTES_PROPERTY, FXCollections.<String, Object>observableHashMap());
/**
* The {@link Graph} which this {@link Node} belongs to.
*/
private Graph graph; // associated graph
/**
* The {@link Graph} that is nested inside of this {@link Node}.
*/
private Graph nestedGraph;
/**
* Constructs a new {@link Node}.
*/
public Node() {
this(new HashMap<String, Object>());
}
/**
* Constructs a new {@link Node} and copies the given
* <i>attributesProperty</i> into the {@link #attributesProperty()
* attributesProperty map} of this {@link Node}.
*
* @param attributes
* A {@link Map} containing the attributesProperty which are
* copied into the {@link #attributesProperty()
* attributesProperty map} of this {@link Node}.
*/
public Node(Map<String, Object> attributes) {
this.attributesProperty.putAll(attributes);
}
@Override
public ReadOnlyMapProperty<String, Object> attributesProperty() {
return attributesProperty.getReadOnlyProperty();
}
/**
* Returns all incoming {@link Edge}s of this {@link Node}. The full graph
* hierarchy is scanned for incoming edges, and not just the
* {@link #getGraph() associated graph}.
*
* @return All incoming {@link Edge}s.
*/
public Set<Edge> getAllIncomingEdges() {
if (graph == null) {
return Collections.emptySet();
}
Set<Edge> incoming = Collections.newSetFromMap(new IdentityHashMap<Edge, Boolean>());
incoming.addAll(getIncomingEdges());
if (graph.getNestingNode() != null) {
incoming.addAll(graph.getNestingNode().getAllIncomingEdges());
}
return incoming;
}
/**
* Returns all neighbors of this {@link Node}. The full graph hierarchy is
* scanned for neighbors, and not just the {@link #getGraph() associated
* graph}.
*
* @return All neighbors.
*/
public Set<Node> getAllNeighbors() {
Set<Node> neighbors = Collections.newSetFromMap(new IdentityHashMap<Node, Boolean>());
neighbors.addAll(getAllPredecessorNodes());
neighbors.addAll(getAllSuccessorNodes());
return neighbors;
}
/**
* Returns all outgoing {@link Edge}s of this {@link Node}. The full graph
* hierarchy is scanned for outgoing edges, and not just the
* {@link #getGraph() associated graph}.
*
* @return All outgoing {@link Edge}s.
*/
public Set<Edge> getAllOutgoingEdges() {
if (graph == null) {
return Collections.emptySet();
}
Set<Edge> outgoing = Collections.newSetFromMap(new IdentityHashMap<Edge, Boolean>());
outgoing.addAll(getOutgoingEdges());
if (graph.getNestingNode() != null) {
outgoing.addAll(graph.getNestingNode().getAllOutgoingEdges());
}
return outgoing;
}
/**
* Returns all predecessor {@link Node}s of this {@link Node}. The full
* graph hierarchy is scanned for predecessor nodes, and not just the
* {@link #getGraph() associated graph}.
*
* @return All predecessor {@link Node}s.
*/
public Set<Node> getAllPredecessorNodes() {
if (graph == null) {
return Collections.emptySet();
}
Set<Node> predecessors = Collections.newSetFromMap(new IdentityHashMap<Node, Boolean>());
predecessors.addAll(getPredecessorNodes());
if (graph.getNestingNode() != null) {
predecessors.addAll(graph.getNestingNode().getAllPredecessorNodes());
}
return predecessors;
}
/**
* Returns all successor {@link Node}s of this {@link Node}. The full graph
* hierarchy is scanned for successor nodes, and not just the
* {@link #getGraph() associated graph}.
*
* @return All successor {@link Node}s.
*/
public Set<Node> getAllSuccessorNodes() {
if (graph == null) {
return Collections.emptySet();
}
Set<Node> successors = Collections.newSetFromMap(new IdentityHashMap<Node, Boolean>());
successors.addAll(getSuccessorNodes());
if (graph.getNestingNode() != null) {
successors.addAll(graph.getNestingNode().getAllSuccessorNodes());
}
return successors;
}
@Override
public ObservableMap<String, Object> getAttributes() {
return attributesProperty.get();
}
/**
* Returns the {@link Graph} to which this {@link Node} belongs.
*
* @return The {@link Graph} to which this {@link Node} belongs.
*/
public Graph getGraph() {
return graph;
}
/**
* Returns the local incoming {@link Edge}s of this {@link Node}. Only the
* {@link #getGraph() associated graph} is scanned for incoming edges, and
* not the whole graph hierarchy.
*
* @return The local incoming {@link Edge}s.
*/
public Set<Edge> getIncomingEdges() {
if (graph == null) {
return Collections.emptySet();
}
Set<Edge> incoming = Collections.newSetFromMap(new IdentityHashMap<Edge, Boolean>());
for (Edge e : graph.getEdges()) {
if (e.getTarget() == this) {
incoming.add(e);
}
}
return incoming;
}
/**
* Returns all (local) neighbors of this {@link Node}, i.e. the union of the
* {@link #getPredecessorNodes()} and {@link #getSuccessorNodes()} .
*
* @return All (local) neighbors of this {@link Node}.
*/
public Set<Node> getNeighbors() {
Set<Node> neighbors = Collections.newSetFromMap(new IdentityHashMap<Node, Boolean>());
neighbors.addAll(getPredecessorNodes());
neighbors.addAll(getSuccessorNodes());
return neighbors;
}
/**
* Returns the {@link Graph} that is nested inside of this {@link Node}. May
* be <code>null</code>.
*
* @return The {@link Graph} that is nested inside of this {@link Node}, or
* <code>null</code>.
*/
public Graph getNestedGraph() {
return nestedGraph;
}
/**
* Returns the local outgoing {@link Edge}s of this {@link Node}. Only the
* {@link #getGraph() associated graph} is scanned for outgoing edges, and
* not the whole graph hierarchy.
*
* @return The local outgoing {@link Edge}s.
*/
public Set<Edge> getOutgoingEdges() {
if (graph == null) {
return Collections.emptySet();
}
Set<Edge> outgoing = Collections.newSetFromMap(new IdentityHashMap<Edge, Boolean>());
for (Edge e : graph.getEdges()) {
if (e.getSource() == this) {
outgoing.add(e);
}
}
return outgoing;
}
/**
* Returns the local predecessor {@link Node}s of this {@link Node}. Only
* the {@link #getGraph() associated graph} is scanned for predecessor
* nodes, and not the whole graph hierarchy.
*
* @return The local predecessor {@link Node}s.
*/
public Set<Node> getPredecessorNodes() {
Set<Node> predecessors = Collections.newSetFromMap(new IdentityHashMap<Node, Boolean>());
for (Edge incoming : getIncomingEdges()) {
predecessors.add(incoming.getSource());
}
return predecessors;
}
/**
* Returns the local successor {@link Node}s of this {@link Node}. Only the
* {@link #getGraph() associated graph} is scanned for successor nodes, and
* not the whole graph hierarchy.
*
* @return The local successor {@link Node}s.
*/
public Set<Node> getSuccessorNodes() {
Set<Node> successors = Collections.newSetFromMap(new IdentityHashMap<Node, Boolean>());
for (Edge outgoing : getOutgoingEdges()) {
successors.add(outgoing.getTarget());
}
return successors;
}
/**
* Sets the {@link Graph} to which this {@link Node} belongs to the given
* value.
*
* @param graph
* The new {@link Graph} for this {@link Node}.
*/
void setGraph(Graph graph) {
if (graph != null && !graph.getNodes().contains(this)) {
throw new IllegalArgumentException("Node is not contained in graph " + graph);
}
this.graph = graph;
}
/**
* Sets the {@link Graph} which is nested inside this {@link Node} to the
* given value.
*
* @param nestedGraph
* The new nested {@link Graph} for this {@link Node}.
*/
public void setNestedGraph(Graph nestedGraph) {
Graph oldNestedGraph = this.nestedGraph;
this.nestedGraph = nestedGraph;
if (oldNestedGraph != null && oldNestedGraph != nestedGraph) {
oldNestedGraph.setNestingNode(null);
}
if (nestedGraph != null && oldNestedGraph != nestedGraph) {
nestedGraph.setNestingNode(this);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Node {");
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("}");
return sb.toString();
}
}