package org.simantics.interop.mapping.data; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import org.simantics.utils.datastructures.hints.HintContext; import org.slf4j.Logger; /** * * * @author Marko Luukkainen * * @param */ public class GraphNode extends HintContext { private static final Logger LOGGER = org.slf4j.LoggerFactory.getLogger(GraphNode.class); protected T data; private List> nodes = new ArrayList>(); private boolean disposed = false; public GraphNode(T data) { this.data = data; } /** * Returns data. * @return */ public T getData() { return data; } @Override public void setHint(Key key, Object value) { _checkDisposed(); if (value == null) return; super.setHint(key, value); } /** * Adds link to the other node. * @param relationName name of the link from this node to the other node. * @param inverseRelationName nam,e of the link from the other node to this node. * @param node the other node. * @return the created link. Returns null if the link cannot be created. */ public Link addLink(String relationName, String inverseRelationName,GraphNode node) { _checkDisposed(); LOGGER.info("Node link " + data + " " + node.data + " " + relationName + " " + inverseRelationName +"\n"); if(containsLink(relationName, node) && node.containsLink(inverseRelationName, this)) { LOGGER.warn("Node " + getData() + " has already given child " + node.getData() + " ,with name " + relationName + " / " + inverseRelationName); return null; } Link rel = _addLink(relationName,node); Link inv = node._addLink(inverseRelationName,this); rel.setInverseLink(inv); inv.setMainLink(rel); return rel; } /** * Adds link to the other node, with
null
link name from the other node to this node. * @param relationName * @param node name of the link from this node to the other node * @return the created link. Returns null if the link cannot be created. */ public Link addLink(String relationName,GraphNode node) { _checkDisposed(); if(containsLink(relationName, node)) { LOGGER.warn("Node " + getData() + " has already given child " + node.getData() + " ,with name " + relationName ); return null; } Link rel = _addLink(relationName,node); Link inv = node._addLink(null,this); rel.setInverseLink(inv); inv.setMainLink(rel); return rel; } /** * Adds link to the other node, with copying link properties from given link. * @param link * @param node name of the link from this node to the other node * @return the created link. Returns null if the link cannot be created. */ public Link addLink(Link link ,GraphNode node) { _checkDisposed(); if(containsLink(link.getName(), node) && node.containsLink(link.getInverseName(), this)) { LOGGER.warn("Node " + getData() + " has already given child " + node.getData() + " ,with name " + link.getName() + " / " + link.getInverseName()); return null; } Link rel = _addLink(link.getName(),node); Link inv = node._addLink(link.getInverseName(),this); rel.setInverseLink(inv); inv.setMainLink(rel); rel.setHints(link.getHints()); inv.setHints(link.getInverse().getHints()); return rel; } protected Link _addLink(String relation, GraphNode node) { Link link = new Link(this, relation,node); nodes.add(link); return link; } protected Link _removeLink(String relation,String inverse, GraphNode node) { for (int i = 0; i < nodes.size(); i++) { Link link = nodes.get(i); if (node.equals(link.to()) && equals(relation,link.getName()) && equals(inverse,link.getInverseName())) { return nodes.remove(i); } } return null; } protected Link _removeLink(String relation) { for (int i = 0; i < nodes.size(); i++) { Link link = nodes.get(i); if (equals(relation,link.getName())) { return nodes.remove(i); } } return null; } protected Link _removeLink(String relation, String inverse) { for (int i = 0; i < nodes.size(); i++) { Link link = nodes.get(i); if (equals(relation,link.getName()) && equals(inverse,link.getInverseName())) { return nodes.remove(i); } } return null; } protected Link _removeLink(String relation, GraphNode node) { for (int i = 0; i < nodes.size(); i++) { Link link = nodes.get(i); if (node.equals(link.to()) && equals(relation,link.getName())) { return nodes.remove(i); } } return null; } protected Link _removeLink(GraphNode node) { for (int i = 0; i < nodes.size(); i++ ) { Link link = nodes.get(i); if (node.equals(link.to())) { return nodes.remove(i); } } return null; } protected boolean _removeLink(Link link) { return nodes.remove(link); } /** * Removes the given link. * @param link * @return true if the link was removed. */ public boolean removeLink(Link link) { _checkDisposed(); if (_removeLink(link)) { if (link.hasInverse()) { link.to()._removeLink(link.getInverse()); } return true; } return false; } /** * Removes a link (or multiple links). * @param relation the name of the link from this node. * @return true if a link was removed. */ public boolean removeLink(String relation) { _checkDisposed(); boolean deleted = false; while (true) { Link removed = _removeLink(relation); if (removed == null) break; if (removed.hasInverse()) { removed.to()._removeLink(removed.getInverse()); deleted = true; } } return deleted; } /** * Removes a link (or multiple links). * @param relation the name of the link from this node. * @return true if the link was removed. */ public boolean removeLink(String relation, String inverse) { _checkDisposed(); boolean deleted = false; while (true) { Link removed = _removeLink(relation, inverse); if (removed == null) break; if (removed.hasInverse()) { removed.to()._removeLink(removed.getInverse()); deleted = true; } } return deleted; } /** * Removes a link. * @param relation the name of the link from this node to the other node. * @param node the other node. * @return true if the link was removed. */ public boolean removeLink(String relation, GraphNode node) { _checkDisposed(); boolean deleted = false; while (true) { Link removed = _removeLink(relation, node); if (removed == null) break; if (removed.hasInverse()) { removed.to()._removeLink(removed.getInverse()); deleted = true; } } return deleted; } /** * Removes all links from this node to the other node. * @param node the other node. * @return */ public boolean removeLink(GraphNode node) { _checkDisposed(); boolean deleted = false; while (true) { Link removed = _removeLink(node); if (removed == null) break; if (removed.hasInverse()) { removed.to()._removeLink(removed.getInverse()); deleted = true; } } return deleted; } /** * Removes a link. * @param relation the name of the link from this node to the other node. * @param inverse the name of the link from the other node to this node. * @param node the other node. * @return true if the link was removed. */ public boolean removeLink(String relation, String inverse, GraphNode node) { _checkDisposed(); Link removed = _removeLink(relation, inverse, node); if (removed != null && removed.hasInverse()) { removed.to()._removeLink(removed.getInverse()); return true; } return false; } public boolean containsLink(GraphNode node) { _checkDisposed(); for (Link link : nodes) { if (node.equals(link.to())) return true; } return false; } public boolean containsLink(String relationName, GraphNode node) { _checkDisposed(); if (relationName == null) return false; for (Link link : nodes) { if (node.equals(link.to()) && equals(relationName,link.getName())) return true; } return false; } public boolean containsLink(String relationName, String inverseName, GraphNode node) { _checkDisposed(); for (Link link : nodes) { if (node.equals(link.to()) && equals(relationName,link.getName()) && equals(inverseName,link.getInverseName())) return true; } return false; } public Collection> getLinks() { _checkDisposed(); Collection> coll = new ArrayList>(nodes); return coll; } public Collection> getNodes(String rel) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(rel,link.getName())) result.add(link.to()); } return result; } public Collection> getLinks(String rel) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(rel,link.getName())) result.add(link); } return result; } public Collection> getLinks(String rel, GraphNode node) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(rel,link.getName()) && link.to().equals(node)) result.add(link); } return result; } public Collection> getLinks(String rel, String inverse) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(rel,link.getName()) && equals(inverse, link.getInverseName())) result.add(link); } return result; } public Collection> getLinks(String rel, String inverse, GraphNode node) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(rel,link.getName()) && equals(inverse, link.getInverseName()) && link.to().equals(node)) result.add(link); } return result; } public Collection> getNodesWithInv(String inv) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(inv,link.getInverseName())) result.add(link.to()); } return result; } public Collection> getNodes(String rel, String inv) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (equals(rel,link.getName()) && equals(inv,link.getInverseName())) result.add(link.to()); } return result; } public static boolean equals(String s1, String s2) { if (s1 != null) return s1.equals(s2); if (s2 != null) return s2.equals(s1); return true; // both null } /** * Returns links from this node to the given node. * @param node * @return */ public Collection> getLinks(GraphNode node) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (link.to().equals(node)) result.add(link); } return result; } /** * Returns names of links from this node to the given node. Does not return null names. * @param node * @return */ public Collection getRelations(GraphNode node) { _checkDisposed(); Collection result = new ArrayList(); for (Link link : nodes) { if (link.to().equals(node) && link.getName() != null) result.add(link.getName()); } return result; } /** * Returns links from given node to this node. * @param node * @return */ public Collection> getInverseLinks(GraphNode node) { _checkDisposed(); Collection> result = new ArrayList>(); for (Link link : nodes) { if (link.to().equals(node)) result.add(link.getInverse()); } return result; } /** * Returns names of links from given node to this node. Does not return null names. * @param node * @return */ public Collection getInverses(GraphNode node) { _checkDisposed(); Collection result = new ArrayList(); for (Link link : nodes) { if (link.to().equals(node) && link.getInverseName() != null) result.add(link.getInverseName()); } return result; } @Override public int hashCode() { return data.hashCode(); } @Override public boolean equals(Object arg0) { if (!arg0.getClass().equals(getClass())) return false; GraphNode other = (GraphNode)arg0; if (this == other) return true; if (this.data == null || other.data == null) return false; if (!data.equals(other.data)) return false; return true; } /** * Merges given nodes to new node. * Does not copy hints! * @param other */ public GraphNode merge(GraphNode other, T mergedData) { _checkDisposed(); GraphNode mergedNode = new GraphNode(mergedData); copyLinks(this, mergedNode); copyLinks(other, mergedNode); //mergedNode.setHints(other.getHints()); //mergedNode.setHints(this.getHints()); this.destroy(); other.destroy(); return mergedNode; } /** * Copies a link to other node. * @param from the node containing original link * @param to the node where link is copied to * @param l the link that is copied. * @return created link, if copy is successful. Otherwise returns null. */ public static Link copyLink(GraphNode from, GraphNode to, Link l) { if (l.from() != from) throw new IllegalArgumentException("Link is not starting from " + from + " node."); if (l.to() == to) return null; if (to.containsLink(l.getName(), l.getInverseName(),l.to())) return null; if (l.isMain()) { Link newLink = to.addLink(l.getName(),l.getInverseName(),l.to()); if (newLink != null) { newLink.setHints(l.getHints()); newLink.getInverse().setHints(l.getInverse().getHints()); return newLink; } else { return null; } } else { Link newLink = l.to().addLink(l.getInverseName(), l.getName(),to); if (newLink != null) { newLink.setHints(l.getInverse().getHints()); newLink.getInverse().setHints(l.getHints()); return newLink.getInverse(); } else { return null; } } } /** * Copies a link to other node, but inverts its direction. * @param from the node containing original link * @param to the node where link is copied to * @param l the link that is copied. * @return created link, if copy is successful. Otherwise returns null. */ public static Link copyLinkInverse(GraphNode from, GraphNode to, Link l) { if (l.from() != from) throw new IllegalArgumentException("Link is not starting from " + from + " node."); if (l.to() == to) return null; if (to.containsLink(l.getInverseName(), l.getName(),l.to())) return null; if (l.isMain()) { Link newLink = l.to().addLink(l.getName(),l.getInverseName(),to); if (newLink != null) { newLink.setHints(l.getHints()); newLink.getInverse().setHints(l.getInverse().getHints()); return newLink; } else { return null; } } else { Link newLink = to.addLink(l.getInverseName(), l.getName(),l.to()); if (newLink != null) { newLink.setHints(l.getInverse().getHints()); newLink.getInverse().setHints(l.getHints()); return newLink.getInverse(); } else { return null; } } } public static void copyLinks(GraphNode from, GraphNode to, Collection> links) { for (Link l : links) { copyLink(from, to, l); } } public static void copyLinks(GraphNode from, GraphNode to) { for (Link l : from.getLinks()) { copyLink(from, to, l); } } /** * Copies a link to other node and removes the original link. If move is not successful, the original link is not removed. * @param from the node containing original link * @param to the node where link is copied to * @param l the link that is moved. * @return created link, if move is successful. Otherwise returns null. */ public static Link moveLink(GraphNode from, GraphNode to, Link l) { Link newLink = copyLink(from, to, l); if (newLink != null) { from.removeLink(l); return newLink; } return null; } public static void moveLinks(GraphNode from, GraphNode to, Collection> links) { for (Link l : links) { Link newLink = copyLink(from, to, l); if (newLink != null) { from.removeLink(l); } } } public static void moveLinkInverses(GraphNode from, GraphNode to, Collection> links) { for (Link l : links) { Link newLink = copyLinkInverse(from, to, l); if (newLink != null) { from.removeLink(l); } } } public static void moveLinks(GraphNode from, GraphNode to) { for (Link l : from.getLinks()) { Link newLink = copyLink(from, to, l); if (newLink != null) { from.removeLink(l); } } } /** * Replaces a link with a link going first to given node, and the to the original destination. * The link names and hints are copied to the created links. * * Example: link is 'a' -> 'b'. With insert attribute 'c', the result is 'a' -> 'c' -> 'b'. * * @param link * @param insert * @return Array containing created links (2). */ @SuppressWarnings("unchecked") public static Link[] insert(Link link, GraphNode insert) { GraphNode a = link.from(); Link c_b = moveLink(a, insert, link); Link a_c = a.addLink(c_b, insert); return new Link[]{a_c,c_b}; } /** * Finds all nodes that have the same relations to the same nodes as this node. * @return */ public Collection> getFullSimilars() { _checkDisposed(); Collection> result = new ArrayList>(); Set> potential = new HashSet>(); for (Link link : nodes) { for (Link link2 : link.to().nodes) { if (!link2.to().equals(this)) potential.add(link2.to()); } } for (GraphNode node : potential) { if (node.nodes.containsAll(nodes)) result.add(node); } return result; } /** * Finds all nodes that have at least one similar relation to the same nodes as this node. * @return */ public Collection> getSemiSimilars() { _checkDisposed(); Collection> result = new ArrayList>(); Set> potential = new HashSet>(); for (Link link : nodes) { for (Link link2 : link.to().nodes) { if (!link2.to().equals(this)) potential.add(link2.to()); } } for (GraphNode node : potential) { for (Link link : nodes) { if (node.nodes.contains(link)) result.add(node); } } return result; } /** * Disposes this node. Removes all the links that connect to this node. */ public void destroy() { if (disposed) return; LOGGER.info("Node destroy " + data + " " + this); Collection> coll = new ArrayList>(); coll.addAll(nodes); nodes.clear(); for (Link link : coll) { link.to()._removeLink(this); } nodes = null; disposed = true; } /** * Removes this node from the graph, and reconnects parents and children of this node. */ public void remove() { if (disposed) return; // FIXME: link.to may be *this. LOGGER.info("Node remove " + data + " " + this); if (nodes.size() == 1) { Link link = nodes.get(0); link.to().removeLink(link.getInverseName(), link.getName(), this); } else { for (int i = 0; i < nodes.size() -1 ; i++) { Link link1 = nodes.get(i); link1.to()._removeLink(link1.getInverseName(), link1.getName(), this); for (int j = i+1; j < nodes.size(); j++) { Link link2 = nodes.get(j); link2.to()._removeLink(link2.getInverseName(), link2.getName(), this); if (link1.to().equals(link2.to())) continue; link1.to().addLink(link1.getInverseName(),link2.getInverseName(),link2.to()); } } } nodes.clear(); nodes = null; disposed = true; } /** * * @return true if the node has been disposed and cannot be used anymore. */ public boolean isDisposed() { return disposed; } /** * Dispose the node and all nodes that are in the same graph. */ public void dispose() { if (disposed) return; Collection> links = new ArrayList>(); links.addAll(nodes); nodes.clear(); for (Link l : links) l.to().dispose(); links.clear(); nodes = null; disposed = true; } protected void _checkDisposed() { if (disposed) { LOGGER.error("Remove Node, disposed " + this); throw new RuntimeException("Node " + this + " is disposed."); } } public int distanceTo(GraphNode node, String rel, String inv) { if (node.equals(this)) return 0; int count = 0; Set> processed = new HashSet>(); Set> next = new HashSet>(); next.add(this); while (true) { if (next.size() == 0) return -1; count++; processed.addAll(next); Set> nextNext = new HashSet>(); for (GraphNode n : next) { for (GraphNode nn : n.getNodes(rel, inv)) if (!processed.contains(nn)) nextNext.add(nn); } next = nextNext; if (next.contains(node)) return count; } } public String toString() { return "Node : " + data; } }