org.neo4j.graphmatching.PatternFinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-graph-matching Show documentation
Show all versions of neo4j-graph-matching Show documentation
A graph pattern matcher for Neo4j.
The newest version!
/*
* Copyright (c) 2002-2017 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.graphmatching;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Stack;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
/**
* Performs the actual finding of matches given the pattern of how a match
* looks like and a {@link Node} to start traversing from.
*/
@Deprecated
class PatternFinder implements Iterable, Iterator
{
private Set visitedRels = new HashSet();
private PatternPosition currentPosition;
private OptionalPatternFinder optionalFinder;
private PatternNode startPatternNode;
private Node startNode;
private Collection optionalNodes;
private boolean optional;
private final PatternMatcher matcher;
PatternFinder( PatternMatcher matcher, PatternNode start, Node startNode )
{
this( matcher, start, startNode, false );
}
PatternFinder( PatternMatcher matcher, PatternNode start, Node startNode,
boolean optional )
{
this.matcher = matcher;
this.startPatternNode = start;
this.startNode = startNode;
currentPosition = new PatternPosition( startNode, start, optional );
this.optional = optional;
}
PatternFinder( PatternMatcher matcher, PatternNode start, Node startNode,
boolean optional, Collection optionalNodes )
{
this( matcher, start, startNode, optional );
this.optionalNodes = optionalNodes;
}
PatternNode getStartPatternNode()
{
return startPatternNode;
}
Node getStartNode()
{
return startNode;
}
/**
* Represents a traversal state so that we can go back to it when we've
* descended the graph and comes back up to continue matching on a
* higher level.
*/
private static class CallPosition
{
private PatternPosition patternPosition;
private Iterator relItr;
private Relationship lastRel;
private PatternRelationship currentPRel;
private boolean popUncompleted;
CallPosition( PatternPosition patternPosition, Relationship lastRel,
Iterator relItr, PatternRelationship currentPRel,
boolean popUncompleted )
{
this.patternPosition = patternPosition;
this.relItr = relItr;
this.lastRel = lastRel;
this.currentPRel = currentPRel;
this.popUncompleted = popUncompleted;
}
public void setLastVisitedRelationship( Relationship rel )
{
this.lastRel = rel;
}
public Relationship getLastVisitedRelationship()
{
return lastRel;
}
public boolean shouldPopUncompleted()
{
return popUncompleted;
}
public PatternPosition getPatternPosition()
{
return patternPosition;
}
public PatternRelationship getPatternRelationship()
{
return currentPRel;
}
public Iterator getRelationshipIterator()
{
return relItr;
}
}
private Stack callStack = new Stack();
private Stack uncompletedPositions =
new Stack();
private Stack foundElements = new Stack();
private PatternMatch findNextMatch()
{
if ( callStack.isEmpty() && currentPosition != null )
{
// Try to find a first indication of a match, i.e. find some part
// of the pattern in the graph.
if ( traverse( currentPosition, true ) )
{
// found first match, return it
currentPosition = null;
return extractPotentialResult();
}
currentPosition = null;
}
else
{
return traverseFromCallStack();
}
return null;
}
private PatternMatch extractPotentialResult()
{
HashMap filteredElements =
new HashMap();
HashMap relElements =
new HashMap();
boolean patternValid = true;
for ( PatternElement element : foundElements )
{
PatternElement other = filteredElements.get( element.getPatternNode() );
if ( other != null && !other.getNode().equals( element.getNode() ) )
{
patternValid = false;
break;
}
filteredElements.put( element.getPatternNode(), element );
relElements.put( element.getFromPatternRelationship(),
element.getFromRelationship() );
}
PatternMatch patternMatch = new PatternMatch( filteredElements,
relElements );
foundElements.pop();
if ( patternValid )
{
return patternMatch;
}
return traverseFromCallStack();
}
private PatternMatch traverseFromCallStack()
{
if ( callStack.isEmpty() )
{
return null;
}
boolean matchFound = false;
do
{
CallPosition callStackInformation = callStack.peek();
matchFound = traverse( callStackInformation );
}
while ( !callStack.isEmpty() && !matchFound );
if ( matchFound )
{
return extractPotentialResult();
}
return null;
}
private boolean traverse( CallPosition callPos )
{
// make everything like it was before we returned previous match
PatternPosition currentPos = callPos.getPatternPosition();
PatternRelationship pRel = callPos.getPatternRelationship();
pRel.mark();
visitedRels.remove( callPos.getLastVisitedRelationship() );
Node currentNode = currentPos.getCurrentNode();
Iterator relItr = callPos.getRelationshipIterator();
while ( relItr.hasNext() )
{
Relationship rel = relItr.next();
if ( visitedRels.contains( rel ) )
{
continue;
}
if ( !checkProperties( pRel, rel ) )
{
continue;
}
Node otherNode = rel.getOtherNode( currentNode );
PatternNode otherPosition = pRel.getOtherNode( currentPos
.getPatternNode() );
pRel.mark();
visitedRels.add( rel );
if ( traverse( new PatternPosition( otherNode, otherPosition, pRel,
rel, optional ), true ) )
{
callPos.setLastVisitedRelationship( rel );
return true;
}
visitedRels.remove( rel );
pRel.unMark();
}
pRel.unMark();
if ( callPos.shouldPopUncompleted() )
{
uncompletedPositions.pop();
}
callStack.pop();
foundElements.pop();
return false;
}
private boolean traverse( PatternPosition currentPos, boolean pushElement )
{
PatternNode pNode = currentPos.getPatternNode();
Node currentNode = currentPos.getCurrentNode();
if ( !checkProperties( pNode, currentNode ) )
{
return false;
}
if ( pushElement )
{
foundElements.push( new PatternElement(
pNode, currentPos.fromPatternRel(),
currentNode, currentPos.fromRelationship() ) );
}
if ( currentPos.hasNext() )
{
boolean popUncompleted = false;
PatternRelationship pRel = currentPos.next();
if ( currentPos.hasNext() )
{
uncompletedPositions.push( currentPos );
popUncompleted = true;
}
assert !pRel.isMarked();
Iterator relItr = getRelationshipIterator( currentPos
.getPatternNode(), currentNode, pRel );
pRel.mark();
while ( relItr.hasNext() )
{
Relationship rel = relItr.next();
if ( visitedRels.contains( rel ) )
{
continue;
}
if ( !checkProperties( pRel, rel ) )
{
continue;
}
Node otherNode = rel.getOtherNode( currentNode );
PatternNode otherPosition = pRel.getOtherNode( currentPos
.getPatternNode() );
visitedRels.add( rel );
CallPosition callPos = new CallPosition( currentPos, rel,
relItr, pRel, popUncompleted );
callStack.push( callPos );
if ( traverse( new PatternPosition( otherNode, otherPosition,
pRel, rel, optional ), true ) )
{
return true;
}
callStack.pop();
visitedRels.remove( rel );
}
pRel.unMark();
if ( popUncompleted )
{
uncompletedPositions.pop();
}
foundElements.pop();
return false;
}
boolean matchFound = true;
if ( !uncompletedPositions.isEmpty() )
{
PatternPosition digPos = uncompletedPositions.pop();
digPos.reset();
matchFound = traverse( digPos, false );
uncompletedPositions.push( digPos );
return matchFound;
}
return true;
}
private Iterator getRelationshipIterator(
PatternNode fromNode, Node currentNode, PatternRelationship pRel )
{
Iterator relItr = null;
if ( pRel.anyRelType() )
{
relItr = currentNode.getRelationships(
pRel.getDirectionFrom( fromNode ) ).iterator();
}
else
{
relItr = currentNode.getRelationships( pRel.getType(),
pRel.getDirectionFrom( fromNode ) ).iterator();
}
return relItr;
}
private boolean checkProperties(
AbstractPatternObject extends PropertyContainer> patternObject,
PropertyContainer object )
{
PropertyContainer associatedObject = patternObject.getAssociation();
if ( associatedObject != null && !object.equals( associatedObject ) )
{
return false;
}
for ( Map.Entry> matchers :
patternObject.getPropertyConstraints() )
{
String key = matchers.getKey();
Object propertyValue = object.getProperty( key, null );
for (ValueMatcher matcher : matchers.getValue() )
{
if ( !matcher.matches( propertyValue ) )
{
return false;
}
}
}
return true;
}
public Iterator iterator()
{
return this;
}
private PatternMatch match = null;
private PatternMatch optionalMatch = null;
public boolean hasNext()
{
if ( match == null )
{
match = findNextMatch();
optionalFinder = null;
}
else if ( optionalNodes != null )
{
if ( optionalFinder == null )
{
optionalFinder = new OptionalPatternFinder( matcher, match,
optionalNodes );
}
if ( optionalMatch == null )
{
optionalMatch = optionalFinder.findNextOptionalPatterns();
}
if ( optionalMatch == null && optionalFinder.anyMatchFound() )
{
match = null;
return hasNext();
}
}
return match != null;
}
public PatternMatch next()
{
if ( match == null )
{
match = findNextMatch();
optionalFinder = null;
}
PatternMatch matchToReturn = match;
PatternMatch optionalMatchToReturn = null;
if ( match != null && optionalNodes != null )
{
if ( optionalFinder == null )
{
optionalFinder = new OptionalPatternFinder( matcher, match,
optionalNodes );
}
if ( optionalMatch == null )
{
optionalMatch = optionalFinder.findNextOptionalPatterns();
}
optionalMatchToReturn = optionalMatch;
optionalMatch = null;
if ( optionalMatchToReturn == null )
{
match = null;
if ( optionalFinder.anyMatchFound() )
{
return next();
}
}
}
else
{
match = null;
}
if ( matchToReturn == null )
{
throw new NoSuchElementException();
}
return optionalMatchToReturn != null ? PatternMatch.merge(
matchToReturn, optionalMatchToReturn ) : matchToReturn;
}
public void remove()
{
throw new UnsupportedOperationException();
}
}