/* * Copyright 2008 Network Engine for Objects in Lund AB [neotechnology.com] * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.graphalgo.benchmark.graphgeneration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.neo4j.api.core.Direction; import org.neo4j.api.core.Node; import org.neo4j.api.core.Relationship; import org.neo4j.api.core.RelationshipType; import org.neo4j.api.core.ReturnableEvaluator; import org.neo4j.api.core.StopEvaluator; import org.neo4j.api.core.TraversalPosition; import org.neo4j.api.core.Traverser; import org.neo4j.api.core.Traverser.Order; import org.neo4j.impl.traversal.TraverserFactory; /** * This class can be used to represent part of a network (like a subgraph), * represented as a set of nodes and a set of edges. All this really does is * filter the results of Node.getRelationships to only return relationships * within this subnetwork, thus limiting traversals and searches to the nodes * within the subnetwork. Therefore, any changes made to the subnetwork will be * reflected in the underlying network as well. The subgraph always starts out * empty, and can be emptied again with the clear() method. It can then be * filled with nodes and edges through the various methods supplied. This class * can of course also be used to retrieve the set of all nodes and the set of * all edges from a network. * @author Patrik Larsson */ public class SubNetwork { Set<Relationship> subNetworkRelationships = new HashSet<Relationship>(); Set<Node> subNetworkNodes = new HashSet<Node>(); TraverserFactory traverserFactory = new TraverserFactory(); /** * Adds a tree to this subnetwork by doing a breadth first search of a given * depth, adding all nodes found and the first edge leading to each node. * @param node * The starting node * @param searchDepth * The search depth. * @param relationshipType * Relation type to traverse. * @param direction * Direction in which to traverse relationships. */ public void addTreeFromCentralNode( Node node, final int searchDepth, RelationshipType relationshipType, Direction direction ) { Traverser traverser = node.traverse( Order.BREADTH_FIRST, new StopEvaluator() { public boolean isStopNode( TraversalPosition currentPos ) { return currentPos.depth() >= searchDepth; } }, ReturnableEvaluator.ALL, relationshipType, direction ); for ( Node node2 : traverser ) { subNetworkNodes.add( node2 ); subNetworkRelationships.add( traverser.currentPosition() .lastRelationshipTraversed() ); } } /** * Makes a search of a given depth from a given node and adds all found * nodes and relationships to this subnetwork. * @param node * The starting node * @param searchDepth * The search depth. * @param relationshipType * Relation type to traverse. * @param direction * Direction in which to traverse relationships. * @param includeBoundaryRelationships * If false, relationships between nodes where the maximum depth * has been reached will not be included since the search depth * is considered to have been exhausted at them. */ public void addSubNetworkFromCentralNode( Node node, final int searchDepth, RelationshipType relationshipType, Direction direction, boolean includeBoundaryRelationships ) { internalAddSubNetworkFromCentralNode( node, searchDepth, relationshipType, direction, includeBoundaryRelationships, new HashMap<Node,Integer>() ); } /** * Same as addSubNetworkFromCentralNode, but the internal version with some * extra data sent along. * @param nodeScanDepths * This stores at what depth a certain node was added so we can * ignore it when we reach it with a lower depth. */ protected void internalAddSubNetworkFromCentralNode( Node node, final int searchDepth, RelationshipType relationshipType, Direction direction, boolean includeBoundaryRelationships, Map<Node,Integer> nodeScanDepths ) { // We stop here if this node has already been scanned and we this time // have a "shorter" way to go beyond it. Integer previousDepth = nodeScanDepths.get( node ); if ( previousDepth != null && previousDepth >= searchDepth ) { return; } subNetworkNodes.add( node ); nodeScanDepths.put( node, searchDepth ); if ( searchDepth == 0 && includeBoundaryRelationships ) { for ( Relationship relationship : node.getRelationships( relationshipType, direction ) ) { if ( subNetworkNodes .contains( relationship.getOtherNode( node ) ) ) { subNetworkRelationships.add( relationship ); } } } if ( searchDepth <= 0 ) { return; } for ( Relationship relationship : node.getRelationships( relationshipType, direction ) ) { subNetworkRelationships.add( relationship ); internalAddSubNetworkFromCentralNode( relationship .getOtherNode( node ), searchDepth - 1, relationshipType, direction, includeBoundaryRelationships, nodeScanDepths ); } } public void clear() { subNetworkRelationships = new HashSet<Relationship>(); subNetworkNodes = new HashSet<Node>(); } protected Relationship filterRelationship( Relationship relationship ) { if ( subNetworkRelationships.contains( relationship ) ) { return relationship; } return null; } protected Iterable<Relationship> filterRelationships( Iterable<Relationship> rels ) { List<Relationship> result = new LinkedList<Relationship>(); for ( Relationship relationship : rels ) { if ( filterRelationship( relationship ) != null ) { result.add( relationship ); } } return result; } class SubNetWorkNode implements Node { Node underlyingNode; public SubNetWorkNode( Node underlyingNode ) { super(); this.underlyingNode = underlyingNode; } /** * @param otherNode * @param type * @return * @see org.neo4j.api.core.Node#createRelationshipTo(org.neo4j.api.core.Node, * org.neo4j.api.core.RelationshipType) */ public Relationship createRelationshipTo( Node otherNode, RelationshipType type ) { return new SubNetworkRelationship( underlyingNode .createRelationshipTo( otherNode, type ) ); } /** * @see org.neo4j.api.core.Node#delete() */ public void delete() { underlyingNode.delete(); } /** * @return * @see org.neo4j.api.core.Node#getId() */ public long getId() { return underlyingNode.getId(); } /** * @param arg0 * @param arg1 * @return * @see org.neo4j.api.core.PropertyContainer#getProperty(java.lang.String, * java.lang.Object) */ public Object getProperty( String arg0, Object arg1 ) { return underlyingNode.getProperty( arg0, arg1 ); } /** * @param arg0 * @return * @see org.neo4j.api.core.PropertyContainer#getProperty(java.lang.String) */ public Object getProperty( String arg0 ) { return underlyingNode.getProperty( arg0 ); } /** * @return * @see org.neo4j.api.core.PropertyContainer#getPropertyKeys() */ public Iterable<String> getPropertyKeys() { return underlyingNode.getPropertyKeys(); } /** * @return * @see org.neo4j.api.core.PropertyContainer#getPropertyValues() */ public Iterable<Object> getPropertyValues() { return underlyingNode.getPropertyValues(); } /** * @return * @see org.neo4j.api.core.Node#getRelationships() */ public Iterable<Relationship> getRelationships() { return filterRelationships( underlyingNode.getRelationships() ); } /** * @param dir * @return * @see org.neo4j.api.core.Node#getRelationships(org.neo4j.api.core.Direction) */ public Iterable<Relationship> getRelationships( Direction dir ) { return filterRelationships( underlyingNode.getRelationships( dir ) ); } /** * @param type * @param dir * @return * @see org.neo4j.api.core.Node#getRelationships(org.neo4j.api.core.RelationshipType, * org.neo4j.api.core.Direction) */ public Iterable<Relationship> getRelationships( RelationshipType type, Direction dir ) { return filterRelationships( underlyingNode.getRelationships( type, dir ) ); } /** * @param types * @return * @see org.neo4j.api.core.Node#getRelationships(org.neo4j.api.core.RelationshipType[]) */ public Iterable<Relationship> getRelationships( RelationshipType... types ) { return filterRelationships( underlyingNode.getRelationships( types ) ); } /** * @param type * @param dir * @return * @see org.neo4j.api.core.Node#getSingleRelationship(org.neo4j.api.core.RelationshipType, * org.neo4j.api.core.Direction) */ public Relationship getSingleRelationship( RelationshipType type, Direction dir ) { return filterRelationship( underlyingNode.getSingleRelationship( type, dir ) ); } /** * @param arg0 * @return * @see org.neo4j.api.core.PropertyContainer#hasProperty(java.lang.String) */ public boolean hasProperty( String arg0 ) { return underlyingNode.hasProperty( arg0 ); } public boolean hasRelationship() { return getRelationships().iterator().hasNext(); } public boolean hasRelationship( RelationshipType... types ) { return getRelationships( types ).iterator().hasNext(); } public boolean hasRelationship( Direction dir ) { return getRelationships( dir ).iterator().hasNext(); } public boolean hasRelationship( RelationshipType type, Direction dir ) { return getRelationships( type, dir ).iterator().hasNext(); } /** * @param arg0 * @return * @see org.neo4j.api.core.PropertyContainer#removeProperty(java.lang.String) */ public Object removeProperty( String arg0 ) { return underlyingNode.removeProperty( arg0 ); } /** * @param arg0 * @param arg1 * @see org.neo4j.api.core.PropertyContainer#setProperty(java.lang.String, * java.lang.Object) */ public void setProperty( String arg0, Object arg1 ) { underlyingNode.setProperty( arg0, arg1 ); } public Traverser traverse( Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType relationshipType, Direction direction ) { if ( direction == null ) { throw new IllegalArgumentException( "Null direction" ); } if ( relationshipType == null ) { throw new IllegalArgumentException( "Null relationship type" ); } // rest of parameters will be validated in traverser package return traverserFactory .createTraverser( traversalOrder, this, relationshipType, direction, stopEvaluator, returnableEvaluator ); } public Traverser traverse( Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, RelationshipType firstRelationshipType, Direction firstDirection, RelationshipType secondRelationshipType, Direction secondDirection ) { if ( firstDirection == null || secondDirection == null ) { throw new IllegalArgumentException( "Null direction, " + "firstDirection=" + firstDirection + "secondDirection=" + secondDirection ); } if ( firstRelationshipType == null || secondRelationshipType == null ) { throw new IllegalArgumentException( "Null rel type, " + "first=" + firstRelationshipType + "second=" + secondRelationshipType ); } // rest of parameters will be validated in traverser package RelationshipType[] types = new RelationshipType[2]; Direction[] dirs = new Direction[2]; types[0] = firstRelationshipType; types[1] = secondRelationshipType; dirs[0] = firstDirection; dirs[1] = secondDirection; return traverserFactory.createTraverser( traversalOrder, this, types, dirs, stopEvaluator, returnableEvaluator ); } public Traverser traverse( Order traversalOrder, StopEvaluator stopEvaluator, ReturnableEvaluator returnableEvaluator, Object... relationshipTypesAndDirections ) { int length = relationshipTypesAndDirections.length; if ( (length % 2) != 0 || length == 0 ) { throw new IllegalArgumentException( "Variable argument should " + " consist of [RelationshipType,Direction] pairs" ); } int elements = relationshipTypesAndDirections.length / 2; RelationshipType[] types = new RelationshipType[elements]; Direction[] dirs = new Direction[elements]; int j = 0; for ( int i = 0; i < elements; i++ ) { Object relType = relationshipTypesAndDirections[j++]; if ( !(relType instanceof RelationshipType) ) { throw new IllegalArgumentException( "Expected RelationshipType at var args pos " + (j - 1) + ", found " + relType ); } types[i] = (RelationshipType) relType; Object direction = relationshipTypesAndDirections[j++]; if ( !(direction instanceof Direction) ) { throw new IllegalArgumentException( "Expected Direction at var args pos " + (j - 1) + ", found " + direction ); } dirs[i] = (Direction) direction; } return traverserFactory.createTraverser( traversalOrder, this, types, dirs, stopEvaluator, returnableEvaluator ); } } public class SubNetworkRelationship implements Relationship { Relationship underlyingRelationship; public SubNetworkRelationship( Relationship underlyingRelationship ) { super(); this.underlyingRelationship = underlyingRelationship; } /** * @see org.neo4j.api.core.Relationship#delete() */ public void delete() { underlyingRelationship.delete(); } /** * @return * @see org.neo4j.api.core.Relationship#getEndNode() */ public Node getEndNode() { return new SubNetWorkNode( underlyingRelationship.getEndNode() ); } /** * @return * @see org.neo4j.api.core.Relationship#getId() */ public long getId() { return underlyingRelationship.getId(); } /** * @return * @see org.neo4j.api.core.Relationship#getNodes() */ public Node[] getNodes() { return new Node[] { getStartNode(), getEndNode() }; } /** * @param node * @return * @see org.neo4j.api.core.Relationship#getOtherNode(org.neo4j.api.core.Node) */ public Node getOtherNode( Node node ) { return new SubNetWorkNode( underlyingRelationship .getOtherNode( node ) ); } /** * @param arg0 * @param arg1 * @return * @see org.neo4j.api.core.PropertyContainer#getProperty(java.lang.String, * java.lang.Object) */ public Object getProperty( String arg0, Object arg1 ) { return underlyingRelationship.getProperty( arg0, arg1 ); } /** * @param arg0 * @return * @see org.neo4j.api.core.PropertyContainer#getProperty(java.lang.String) */ public Object getProperty( String arg0 ) { return underlyingRelationship.getProperty( arg0 ); } /** * @return * @see org.neo4j.api.core.PropertyContainer#getPropertyKeys() */ public Iterable<String> getPropertyKeys() { return underlyingRelationship.getPropertyKeys(); } /** * @return * @see org.neo4j.api.core.PropertyContainer#getPropertyValues() */ public Iterable<Object> getPropertyValues() { return underlyingRelationship.getPropertyValues(); } /** * @return * @see org.neo4j.api.core.Relationship#getStartNode() */ public Node getStartNode() { return new SubNetWorkNode( underlyingRelationship.getStartNode() ); } /** * @return * @see org.neo4j.api.core.Relationship#getType() */ public RelationshipType getType() { return underlyingRelationship.getType(); } /** * @param arg0 * @return * @see org.neo4j.api.core.PropertyContainer#hasProperty(java.lang.String) */ public boolean hasProperty( String arg0 ) { return underlyingRelationship.hasProperty( arg0 ); } /** * @param type * @return * @see org.neo4j.api.core.Relationship#isType(org.neo4j.api.core.RelationshipType) */ public boolean isType( RelationshipType type ) { return underlyingRelationship.isType( type ); } /** * @param arg0 * @return * @see org.neo4j.api.core.PropertyContainer#removeProperty(java.lang.String) */ public Object removeProperty( String arg0 ) { return underlyingRelationship.removeProperty( arg0 ); } /** * @param arg0 * @param arg1 * @see org.neo4j.api.core.PropertyContainer#setProperty(java.lang.String, * java.lang.Object) */ public void setProperty( String arg0, Object arg1 ) { underlyingRelationship.setProperty( arg0, arg1 ); } } }