/******************************************************************************* * 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 (itemis AG) - refactoring of builder API (bug #480293) * *******************************************************************************/ package org.eclipse.gef.graph; import java.util.AbstractMap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; 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; /** * An {@link Edge} represents a (directed) connection between two {@link Node}s * in a {@link Graph}. * * @author Fabian Steeg * @author anyssen * */ public class Edge implements IAttributeStore { /** * The {@link Builder} can be used to construct an {@link Edge} little by * little. */ public static class Builder { private List<Entry<Object, Object>> attr = new ArrayList<>(); private Graph.Builder.Context context; private Object sourceNodeOrKey; private Object targetNodeOrKey; /** * Constructs a new (anonymous) {@link Edge.Builder} for the given * {@link Context}. * * @param context * The context in which the {@link Edge.Builder} is used. * @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}). */ public Builder(Graph.Builder.Context context, Object sourceNodeOrKey, Object targetNodeOrKey) { this.context = context; if (context != null) { context.edgeBuilders.add(this); } this.sourceNodeOrKey = sourceNodeOrKey; this.targetNodeOrKey = targetNodeOrKey; } /** * Constructs a new (anonymous) context-free {@link Edge.Builder}, which * can only be used to construct a single edge via {@link #buildEdge()}, * i.e. which cannot be chained. * * @param sourceNode * The source {@link Node}. * * @param targetNode * The target {@link Node}. */ public Builder(Node sourceNode, Node targetNode) { this(null, sourceNode, targetNode); } /** * Uses the given setter to set the attribute value. * * @param <T> * The attribute type. * * @param setter * The setter to apply. * @param value * The value to apply. * @return <code>this</code> for convenience. */ public <T> Edge.Builder attr(BiConsumer<Edge, 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 Edge#attributesProperty() attributesProperty map} of the * {@link Edge} 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 Edge.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() { if (context == null) { throw new IllegalStateException("The builder is not chained."); } return context.builder.build(); } /** * Creates a new {@link Edge}, setting the values specified via this * {@link Edge.Builder}. * * @return A newly created {@link Edge}. */ @SuppressWarnings({ "unchecked", "rawtypes" }) public Edge buildEdge() { if (context == null && !(sourceNodeOrKey instanceof Node)) { throw new IllegalStateException("May only use builder keys in case of chained builders."); } if (context == null && !(targetNodeOrKey instanceof Node)) { throw new IllegalStateException("May only use builder keys in case of chained builders."); } Node sourceNode = sourceNodeOrKey instanceof Node ? (Node) sourceNodeOrKey : context.builder.findOrCreateNode(sourceNodeOrKey); Node targetNode = targetNodeOrKey instanceof Node ? (Node) targetNodeOrKey : context.builder.findOrCreateNode(targetNodeOrKey); Edge e = new Edge(sourceNode, targetNode); for (Entry<Object, Object> s : attr) { if (s.getKey() instanceof String) { e.attributesProperty().put((String) s.getKey(), s.getValue()); } else { ((BiConsumer) s.getKey()).accept(e, s.getValue()); } } return e; } /** * 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; } /** * Constructs a new (anonymous) {@link Node.Builder} for a node. * * @return A {@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} for a node. * * @param key * The key that can be used to identify the * {@link Node.Builder} * * @return A {@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()); private Node source; private Node target; private Graph graph; // associated graph /** * Constructs a new {@link Edge} which connects the given <i>source</i> * {@link Node} with the given <i>target</i> {@link Node}. The given * <i>attributesProperty</i> are copied into the * {@link #attributesProperty() attributesProperty map} of this {@link Edge} * . * * @param attributes * A {@link Map} containing the attributesProperty which are * copied into the {@link #attributesProperty() * attributesProperty map} of this {@link Edge}. * @param source * The source {@link Node} for this {@link Edge}. * @param target * The target {@link Node} for this {@link Edge}. */ public Edge(Map<String, Object> attributes, Node source, Node target) { this.attributesProperty.putAll(attributes); this.source = source; this.target = target; } /** * Constructs a new {@link Edge} which connects the given <i>source</i> * {@link Node} with the given <i>target</i> {@link Node}. * * @param source * The source {@link Node} for this {@link Edge}. * @param target * The target {@link Node} for this {@link Edge}. */ public Edge(Node source, Node target) { this(new HashMap<String, Object>(), source, target); } @Override public ReadOnlyMapProperty<String, Object> attributesProperty() { return attributesProperty.getReadOnlyProperty(); } @Override public ObservableMap<String, Object> getAttributes() { return attributesProperty.get(); } /** * Returns the {@link Graph} to which this {@link Edge} belongs. * * @return The {@link Graph} to which this {@link Edge} belongs. */ public Graph getGraph() { return graph; } /** * Returns the source {@link Node} of this {@link Edge}. * * @return The source {@link Node} of this {@link Edge}. */ public Node getSource() { return source; } /** * Returns the target {@link Node} of this {@link Edge}. * * @return The target {@link Node} of this {@link Edge}. */ public Node getTarget() { return target; } /** * Sets the {@link Graph} to which this {@link Edge} belongs to the given * value. * * @param graph * The new {@link Graph} for this {@link Edge}. */ void setGraph(Graph graph) { if (graph != null && !graph.getEdges().contains(this)) { throw new IllegalArgumentException("Edge is not contained in graph " + graph); } this.graph = graph; } /** * Sets the source {@link Node} of this {@link Edge} to the given value. * * @param source * The new source {@link Node} for this {@link Edge}. */ public void setSource(Node source) { this.source = source; } /** * Sets the target {@link Node} of this {@link Edge} to the given value. * * @param target * The new target {@link Node} for this {@link Edge}. */ public void setTarget(Node target) { this.target = target; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Edge {"); 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("} from " + getSource() + " to " + getTarget()); return sb.toString(); } }