/*
* This file is part of the HyperGraphDB source distribution. This is copyrighted
* software. For permitted uses, licensing options and redistribution, please see
* the LicensingInformation file at the root level of the distribution.
*
* Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved.
*/
package org.hypergraphdb.algorithms;
import java.util.Iterator;
import org.hypergraphdb.HGHandle;
import org.hypergraphdb.HGException;
import org.hypergraphdb.HGLink;
import org.hypergraphdb.HGSearchResult;
import org.hypergraphdb.HyperGraph;
import org.hypergraphdb.query.HGAtomPredicate;
import org.hypergraphdb.util.CloseMe;
import org.hypergraphdb.util.Pair;
import org.hypergraphdb.util.TempLink;
/**
* <p>
* A default implementation of the <code>HGALGenerator</code> that should cover most
* common cases. In the description below, the term <em>focus atom</em> is used to refer
* to the atom whose adjacency list is being generated.
* </p>
*
* <p>
* The adjacency list generation process is conceptually split into two main steps:
*
* <ol>
* <li>Get the relevant links for the atom.</li>
* <li>For each link, get the relevant members of its outgoing set.</li>
* </ol>
*
* In the simplest case, step 1 amounts to retrieving the incidence set of the focus atom
* and considering only links that point to other atoms besides it (i.e. links with
* arity > 1), while step 2 amounts to retrieving all atoms from the currently considered
* link that are different from the focus atom.
* </p>
*
* <p>
* Step 1 may be augmented with a filter to select links only satisfying certain criteria.
* This filter is configured in the form of a link predicate, a <code>HGQueryCondition</code>.
* </p>
*
* <p>
* Step 2 may be configured to treat links as ordered. When links are interpreted as ordered, there
* are several further options:
*
* <ul>
* <li>The ordering of the outgoing set may be the one implied by the link instance or its reverse.</li>
* <li>Given the position of the focus atom within a link's outgoing set, the generator may
* return only the siblings that occur <strong>after</strong> that position or <strong>before</strong>
* or <strong>both</strong>. Choosing to return both only makes sense when the outgoing set is
* processed in reverse order, for otherwise the behavior is the same as if a link is treated
* as unordered.</li>
* </ul>
*
* In addition, step 2 may also filter the sibling atoms by a general predicate similarly to
* the way links from the incidence set are filtered.
* </p>
*
* <p>
* All of the above mentioned options are configured at construction time. In the simplest case
* of no link or sibling filtering and where links are unordered, use the <code>SimpleALGenerator</code>
* instead which will be somewhat faster.
* </p>
*
* @author Borislav Iordanov
*/
public class DefaultALGenerator implements HGALGenerator, CloseMe
{
protected HyperGraph hg;
private TempLink tempLink = new TempLink(HyperGraph.EMPTY_HANDLE_SET);
private HGAtomPredicate linkPredicate;
private HGAtomPredicate siblingPredicate;
private boolean returnPreceeding = true,
returnSucceeding = true,
reverseOrder = false,
returnSource = false;
private AdjIterator currIterator = null;
protected class AdjIterator implements HGSearchResult<Pair<HGHandle, HGHandle>>
{
HGHandle src;
Iterator<HGHandle> linksIterator;
HGLink currLink;
HGHandle hCurrLink;
Pair<HGHandle, HGHandle> current;
TargetSetIterator tsIter;
boolean closeResultSet;
int minArity = 2;
//
// TargetSetIterator is used to iterate within the target set of a given link.
// It takes care of the 'siblingPredicate'. There are two version of it:
// FTargetSetIterator (starting from 0 and going forward in the target set) and
// BTargetSetIterator (starting from arity-1 and going backward in the target set).
// The two version all almost identical, except that the first increments the 'pos'
// index into the target set while the second decrements it.
//
private abstract class TargetSetIterator
{
boolean focus_seen = false;
int pos = 0;
abstract void reset();
abstract void advance();
boolean hasNext() { return pos != -1; }
public HGHandle next()
{
HGHandle rvalue = currLink.getTargetAt(pos);
advance();
return rvalue;
}
}
final class FTargetSetIterator extends TargetSetIterator
{
void filter()
{
while (true)
{
HGHandle h = currLink.getTargetAt(pos);
if (!focus_seen && h.equals(src))
{
focus_seen = true;
if (returnSource && siblingPredicate.satisfies(hg, h))
return;
else if (!returnSucceeding)
{
pos = -1;
return;
}
}
else if (siblingPredicate.satisfies(hg, h))
return;
if (++pos == currLink.getArity())
{
pos = -1;
return;
}
}
}
void advance()
{
if (++pos == currLink.getArity())
{
pos = -1;
return;
}
else if (siblingPredicate != null)
filter();
else if (!focus_seen && currLink.getTargetAt(pos).equals(src))
{
focus_seen = true;
if (returnSource)
return;
else if (!returnSucceeding || ++pos == currLink.getArity())
pos = -1;
}
}
void reset()
{
pos = 0;
focus_seen = false;
if (!returnPreceeding)
{
while (!currLink.getTargetAt(pos++).equals(src));
focus_seen = true;
if (returnSource &&
(siblingPredicate == null || siblingPredicate.satisfies(hg, src)))
{
pos--;
return;
}
else if (pos == currLink.getArity())
{
pos = -1;
return;
}
}
if (siblingPredicate != null)
filter();
else if (!focus_seen && currLink.getTargetAt(pos).equals(src))
{
focus_seen = true;
if (returnSource && (siblingPredicate == null || siblingPredicate.satisfies(hg, src)))
return;
else if (!returnSucceeding)
{
pos = -1;
return;
}
else
pos++;
}
}
}
// this is almost identical to FTargetSetIterator above, except we decrement the 'pos'
// cursor instead of incrementing it.
final class BTargetSetIterator extends TargetSetIterator
{
void filter()
{
while (true)
{
HGHandle h = currLink.getTargetAt(pos);
if (!focus_seen && h.equals(src))
{
focus_seen = true;
if (returnSource && siblingPredicate.satisfies(hg, src))
return;
else if (!returnSucceeding)
{
pos = -1;
return;
}
}
else if (siblingPredicate.satisfies(hg, h))
return;
if (--pos == -1)
{
return;
}
}
}
void reset()
{
pos = currLink.getArity() - 1;
focus_seen = false;
if (!returnPreceeding)
{
while (!currLink.getTargetAt(pos--).equals(src));
focus_seen = true;
if (returnSource && (siblingPredicate == null || siblingPredicate.satisfies(hg, src)))
{
pos++;
return;
}
else if (pos == -1)
return;
}
if (siblingPredicate != null)
filter();
else if (!focus_seen && currLink.getTargetAt(pos).equals(src))
{
focus_seen = true;
if (returnSource && (siblingPredicate == null || siblingPredicate.satisfies(hg, src)))
return;
else if (!returnSucceeding)
{
pos = -1;
return;
}
else
pos--;
}
}
void advance()
{
if (--pos == -1)
return;
else if (siblingPredicate != null)
filter();
else if (!focus_seen && currLink.getTargetAt(pos).equals(src))
{
focus_seen = true;
if (returnSource)
return;
if (!returnSucceeding)
pos = -1;
else
pos--;
}
}
}
void getNextLink()
{
// loop makes sure that we skip links that only point to our 'src' atom and nothing else
while (true)
{
if (!linksIterator.hasNext())
{
currLink = null;
if (closeResultSet)
((HGSearchResult<HGHandle>)linksIterator).close();
return;
}
hCurrLink = linksIterator.next();
if (linkPredicate != null && !linkPredicate.satisfies(hg, hCurrLink))
continue;
if (hg.isLoaded(hCurrLink))
currLink = (HGLink)hg.get(hCurrLink);
else
{
tempLink.setHandleArray(hg.getStore().getLink(hg.getPersistentHandle(hCurrLink)), 2);
currLink = tempLink;
}
if (currLink.getArity() < minArity)
continue;
tsIter.reset();
if (tsIter.hasNext())
break;
}
}
public AdjIterator(HGHandle src, Iterator<HGHandle> linksIterator, boolean closeResultSet)
{
this.src = src;
this.linksIterator = linksIterator;
this.closeResultSet = closeResultSet;
if (reverseOrder)
tsIter = new BTargetSetIterator();
else
tsIter = new FTargetSetIterator();
if (returnSource)
minArity = 1;
getNextLink();
}
public void remove() { throw new UnsupportedOperationException(); }
public boolean hasNext()
{
return currLink != null;
}
public Pair<HGHandle, HGHandle> next()
{
current = new Pair<HGHandle, HGHandle>(hCurrLink, tsIter.next());
if (!tsIter.hasNext())
getNextLink();
return current;
}
public void close()
{
if (closeResultSet)
((HGSearchResult<HGHandle>)linksIterator).close();
}
public Pair<HGHandle, HGHandle> current()
{
return current;
}
public boolean isOrdered()
{
return false;
}
public boolean hasPrev() { throw new UnsupportedOperationException(); }
public Pair<HGHandle, HGHandle> prev() { throw new UnsupportedOperationException(); }
}
/**
* <p>
* Default constructor available, but the class is not really default constructible -
* you must at least specify a <code>HyperGraph</code> instance on which to operate.
* </p>
*/
public DefaultALGenerator()
{
}
/**
* <p>
* Construct with default values: no link or sibling predicate, returning all
* siblings in their normal storage order.
* </p>
*/
public DefaultALGenerator(HyperGraph graph)
{
this.hg = graph;
}
/**
* <p>
* Construct a default adjacency list generator where links are considered <strong>unordered</strong>.
* </p>
*
* @param hg The HyperGraph instance from where incidence sets are fetched.
* @param linkPredicate The predicate by which links are filtered. Only links satisfying
* this predicate will be considered. If this parameter is <code>null</code>, all links
* from the incidence set will be considered.
* @param siblingPredicate The predicate by which sibling atoms are filtered from the
* adjacency list. Only atoms satisfying this predicate will be returned. If this parameter
* is <code>null</code>, all sibling atoms will be considered.
*/
public DefaultALGenerator(HyperGraph hg,
HGAtomPredicate linkPredicate,
HGAtomPredicate siblingPredicate)
{
this.hg = hg;
this.linkPredicate = linkPredicate;
this.siblingPredicate = siblingPredicate;
}
/**
* <p>
* Construct a default adjacency list generator where links are considered <strong>ordered</strong>.
* </p>
*
* <p>
* The constructor does NOT allow both <code>returnSucceeding</code> and <code>returnPreceeding</code>
* to be set to <code>false</code>. This will always return empty adjacency lists and does not make
* any sense. Even, in a more complex situation where those parameters are determined at run-time
* following some unforeseen logic, the caller must make sure that not both of those parameters are
* false.
* </p>
*
* @param hg The HyperGraph instance from where incidence sets are fetched.
* @param linkPredicate The predicate by which links are filtered. Only links satisfying
* this predicate will be considered. If this parameter is <code>null</code>, all links
* from the incidence set will be considered.
* @param siblingPredicate The predicate by which sibling atoms are filtered from the
* adjacency list. Only atoms satisfying this predicate will be returned. If this parameter
* is <code>null</code>, all sibling atoms will be considered.
* @param returnPreceeding Whether or not to return siblings that appear before the focus
* atom in an ordered link.
* @param returnSucceding Whether or not to return siblings that appear after the focus atom
* in an ordered link.
* @param reverseOrder Whether or not to reverse the default order implied by a link's target
* array. Note that this parameter affects the meaning of <em>preceeding</em> and <em>succeeding</em>
* in the above two parameters.
*/
public DefaultALGenerator(HyperGraph hg,
HGAtomPredicate linkPredicate,
HGAtomPredicate siblingPredicate,
boolean returnPreceeding,
boolean returnSucceeding,
boolean reverseOrder)
{
this.hg = hg;
this.linkPredicate = linkPredicate;
this.siblingPredicate = siblingPredicate;
this.returnPreceeding = returnPreceeding;
this.returnSucceeding = returnSucceeding;
this.reverseOrder = reverseOrder;
if (!returnPreceeding && !returnSucceeding)
throw new HGException("DefaultALGenerator: attempt to construct with both returnSucceeding and returnPreceeding set to false.");
}
/**
* <p>
* Construct a default adjacency list generator where links are considered <strong>ordered</strong>.
* </p>
*
* <p>
* The constructor does NOT allow both <code>returnSucceeding</code> and <code>returnPreceeding</code>
* to be set to <code>false</code>. This will always return empty adjacency lists and does not make
* any sense. Even, in a more complex situation where those parameters are determined at run-time
* following some unforeseen logic, the caller must make sure that not both of those parameters are
* false.
* </p>
*
* @param graph The HyperGraph instance from where incidence sets are fetched.
* @param linkPredicate The predicate by which links are filtered. Only links satisfying
* this predicate will be considered. If this parameter is <code>null</code>, all links
* from the incidence set will be considered.
* @param siblingPredicate The predicate by which sibling atoms are filtered from the
* adjacency list. Only atoms satisfying this predicate will be returned. If this parameter
* is <code>null</code>, all sibling atoms will be considered.
* @param returnPreceeding Whether or not to return siblings that appear before the focus
* atom in an ordered link.
* @param returnSucceding Whether or not to return siblings that appear after the focus atom
* in an ordered link.
* @param reverseOrder Whether or not to reverse the default order implied by a link's target
* array. Note that this parameter affects the meaning of <em>preceeding</em> and <em>succeeding</em>
* in the above two parameters.
* @param returnSource Whether to return the source/originating atom along with its siblings. The default
* is false.
*/
public DefaultALGenerator(HyperGraph graph,
HGAtomPredicate linkPredicate,
HGAtomPredicate siblingPredicate,
boolean returnPreceeding,
boolean returnSucceeding,
boolean reverseOrder,
boolean returnSource)
{
this.hg = graph;
this.linkPredicate = linkPredicate;
this.siblingPredicate = siblingPredicate;
this.returnPreceeding = returnPreceeding;
this.returnSucceeding = returnSucceeding;
this.reverseOrder = reverseOrder;
this.returnSource = returnSource;
// if (!returnPreceeding && !returnSucceeding && !returnSource)
// throw new HGException("DefaultALGenerator: attempt to construct with both returnSucceeding and returnPreceeding set to false.");
}
public HGSearchResult<Pair<HGHandle, HGHandle>> generate(HGHandle h)
{
return new AdjIterator(h,
hg.getIncidenceSet(h).getSearchResult(),
true);
}
public void close()
{
if (currIterator != null && currIterator.closeResultSet)
((HGSearchResult<HGHandle>)currIterator.linksIterator).close();
}
public HyperGraph getGraph()
{
return hg;
}
public void setGraph(HyperGraph graph)
{
this.hg = graph;
}
public HGAtomPredicate getLinkPredicate()
{
return linkPredicate;
}
public void setLinkPredicate(HGAtomPredicate linkPredicate)
{
this.linkPredicate = linkPredicate;
}
public HGAtomPredicate getSiblingPredicate()
{
return siblingPredicate;
}
public void setSiblingPredicate(HGAtomPredicate siblingPredicate)
{
this.siblingPredicate = siblingPredicate;
}
public boolean isReturnPreceeding()
{
return returnPreceeding;
}
public void setReturnPreceeding(boolean returnPreceeding)
{
this.returnPreceeding = returnPreceeding;
}
public boolean isReturnSucceeding()
{
return returnSucceeding;
}
public void setReturnSucceeding(boolean returnSucceeding)
{
this.returnSucceeding = returnSucceeding;
}
public boolean isReverseOrder()
{
return reverseOrder;
}
public void setReverseOrder(boolean reverseOrder)
{
this.reverseOrder = reverseOrder;
}
public boolean isReturnSource()
{
return returnSource;
}
public void setReturnSource(boolean returnSource)
{
this.returnSource = returnSource;
}
}