package matching;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import static org.neo4j.graphdb.DynamicRelationshipType.withName;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.ReturnableEvaluator;
import org.neo4j.graphdb.StopEvaluator;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.TraversalPosition;
import org.neo4j.graphdb.Traverser.Order;
import org.neo4j.graphmatching.PatternMatch;
import org.neo4j.graphmatching.PatternMatcher;
import org.neo4j.graphmatching.PatternNode;
import org.neo4j.helpers.collection.IteratorWrapper;
import org.neo4j.kernel.EmbeddedGraphDatabase;
public class TestMatchingOfCircularPattern
{
static private final boolean STATIC_PATTERN = false;
private static class VisibleMessagesByFollowedUsers implements
Iterable<Node>
{
private final PatternNode start = new PatternNode();
private final PatternNode message = new PatternNode();
private final Node startNode;
public VisibleMessagesByFollowedUsers( Node startNode )
{
this.startNode = startNode;
if ( !STATIC_PATTERN ) start.setAssociation( startNode );
PatternNode user = new PatternNode();
start.createRelationshipTo( user, withName( "FOLLOWS" ) );
user.createRelationshipTo( message, withName( "CREATED" ) );
message.createRelationshipTo( start, withName( "IS_VISIBLE_BY" ) );
}
public Iterator<Node> iterator()
{
Iterable<PatternMatch> matches = PatternMatcher.getMatcher().match(
start, startNode );
return new IteratorWrapper<Node, PatternMatch>( matches.iterator() )
{
@Override
protected Node underlyingObjectToObject( PatternMatch match )
{
return match.getNodeFor( message );
}
};
}
}
private static final int EXPECTED_VISIBLE_MESSAGE_COUNT = 3;
private static Node user;
public static void setupGraph()
{
user = graphdb.createNode();
Node user1 = graphdb.createNode(), user2 = graphdb.createNode(), user3 = graphdb.createNode();
user.createRelationshipTo( user1, withName( "FOLLOWS" ) );
user1.createRelationshipTo( user3, withName( "FOLLOWS" ) );
user.createRelationshipTo( user2, withName( "FOLLOWS" ) );
createMessage( user, "invisible", user1, user2 );
createMessage( user1, "visible", user, user2, user3 );
createMessage( user1, "visible", user );
createMessage( user2, "visible", user, user1 );
createMessage( user2, "invisible", user1, user3 );
createMessage( user3, "invisible", user1, user2 );
createMessage( user3, "invisible", user );
}
private static void createMessage( Node creator, String text,
Node... visibleBy )
{
Node message = graphdb.createNode();
message.setProperty( "text", text );
creator.createRelationshipTo( message, withName( "CREATED" ) );
for ( Node user : visibleBy )
{
message.createRelationshipTo( user, withName( "IS_VISIBLE_BY" ) );
}
}
@Test
public void straightPathsWork()
{
Node start = graphdb.createNode();
Node u1 = graphdb.createNode(), u2 = graphdb.createNode(), u3 = graphdb.createNode();
start.createRelationshipTo( u1, withName( "FOLLOWS" ) );
start.createRelationshipTo( u2, withName( "FOLLOWS" ) );
start.createRelationshipTo( u3, withName( "FOLLOWS" ) );
createMessage( u1, "visible", start );
createMessage( u2, "visible", start );
createMessage( u3, "visible", start );
for ( Node message : new VisibleMessagesByFollowedUsers( start ) )
{
verifyMessage( message );
}
tx.success();
}
@Test
public void messageNodesAreOnlyReturnedOnce()
{
Map<Node, Integer> counts = new HashMap<Node, Integer>();
for ( Node message : new VisibleMessagesByFollowedUsers( user ) )
{
Integer seen = counts.get( message );
counts.put( message, seen == null ? 1 : ( seen + 1 ) );
count++;
}
StringBuilder duplicates = null;
for ( Map.Entry<Node, Integer> seen : counts.entrySet() )
{
if ( seen.getValue() > 1 )
{
if ( duplicates == null )
{
duplicates = new StringBuilder(
"These nodes occured multiple times (expected once): " );
}
else
{
duplicates.append( ", " );
}
duplicates.append( seen.getKey() );
duplicates.append( " (" );
duplicates.append( seen.getValue() );
duplicates.append( " times)" );
}
}
if ( duplicates != null )
{
fail( duplicates.toString() );
}
tx.success();
}
@Test
public void canFindMessageNodesThroughGraphMatching()
{
for ( Node message : new VisibleMessagesByFollowedUsers( user ) )
{
verifyMessage( message );
}
tx.success();
}
@Test
public void canFindMessageNodesThroughTraversing()
{
for ( Node message : traverse( user ) )
{
verifyMessage( message );
}
tx.success();
}
private void verifyMessage( Node message )
{
assertNotNull( message );
assertEquals( "visible", message.getProperty( "text", null ) );
count++;
}
private int count;
private Transaction tx;
@Before
public void resetCount()
{
count = 0;
tx = graphdb.beginTx();
}
@After
public void verifyCount()
{
tx.finish();
tx = null;
assertEquals( EXPECTED_VISIBLE_MESSAGE_COUNT, count );
}
private static Iterable<Node> traverse( final Node startNode )
{
return startNode.traverse( Order.BREADTH_FIRST, stopAtDepth( 2 ),
new ReturnableEvaluator()
{
public boolean isReturnableNode( TraversalPosition pos )
{
Node node = pos.currentNode();
return isMessage( node )
&& isVisibleTo( node, startNode );
}
}, withName( "FOLLOWS" ), Direction.OUTGOING,
withName( "CREATED" ), Direction.OUTGOING );
}
public static StopEvaluator stopAtDepth( final int depth )
{
return new StopEvaluator()
{
public boolean isStopNode( TraversalPosition currentPos )
{
return currentPos.depth() >= depth;
}
};
}
static boolean isMessage( Node node )
{
return node.hasProperty( "text" );
}
static boolean isVisibleTo( Node message, Node user )
{
for ( Relationship visibility : message.getRelationships(
withName( "IS_VISIBLE_BY" ), Direction.OUTGOING ) )
{
if ( visibility.getEndNode().equals( user ) )
{
return true;
}
}
return false;
}
private static GraphDatabaseService graphdb;
@BeforeClass
public static void setUpDb()
{
graphdb = new EmbeddedGraphDatabase( "target/var/db" );
Transaction tx = graphdb.beginTx();
try
{
setupGraph();
tx.success();
}
finally
{
tx.finish();
}
}
@AfterClass
public static void stopGraphdb()
{
graphdb.shutdown();
graphdb = null;
}
}