package org.teiid.query.xquery.saxon;
import java.util.LinkedList;
import java.util.List;
import net.sf.saxon.event.ProxyReceiver;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.expr.parser.PathMap.PathMapArc;
import net.sf.saxon.expr.parser.PathMap.PathMapNode;
import net.sf.saxon.expr.parser.PathMap.PathMapRoot;
import net.sf.saxon.om.AxisInfo;
import net.sf.saxon.om.NamespaceBinding;
import net.sf.saxon.om.NodeName;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.SchemaType;
import net.sf.saxon.type.SimpleType;
import net.sf.saxon.type.Type;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
/**
* A filter that uses the PathMap to determine what should be included in the document
*
* TODO: optimize filtering by not reconstructing the matchcontexts
* TODO: we may still need to do xom/nux style handling of large results, but
* that requires more analysis to determine subtree independence
*/
class PathMapFilter extends ProxyReceiver {
static class MatchContext {
List<PathMapArc> elementArcs;
List<PathMapArc> attributeArcs;
boolean matchedElement;
boolean matchesText;
boolean matchesComment;
void bulidContext(PathMapNode node) {
for (PathMapArc arc : node.getArcs()) {
processArc(arc);
}
}
void processArc(PathMapArc arc) {
NodeTest test = arc.getNodeTest();
if (test == null) {
addAnyNodeArc(arc);
} else {
switch (test.getPrimitiveType()) {
case Type.TEXT:
matchesText = true;
case Type.NODE:
addAnyNodeArc(arc);
break;
case Type.COMMENT:
matchesComment = true;
break;
case Type.ELEMENT:
addElementArc(arc);
break;
case Type.ATTRIBUTE:
addAttributeArc(arc);
break;
}
}
}
private void addAnyNodeArc(PathMapArc arc) {
if (arc.getAxis() == AxisInfo.ATTRIBUTE) {
addAttributeArc(arc);
return;
}
addElementArc(arc);
addAttributeArc(arc);
matchesText = true;
matchesComment = true;
}
private void addAttributeArc(PathMapArc arc) {
if (attributeArcs == null) {
attributeArcs = new LinkedList<PathMapArc>();
}
attributeArcs.add(arc);
}
private void addElementArc(PathMapArc arc) {
if (elementArcs == null) {
elementArcs = new LinkedList<PathMapArc>();
}
elementArcs.add(arc);
}
}
private boolean closed;
private LinkedList<MatchContext> matchContext = new LinkedList<MatchContext>();
private boolean logTrace = LogManager.isMessageToBeRecorded(LogConstants.CTX_RUNTIME, MessageLevel.TRACE);
public PathMapFilter(PathMapRoot root, Receiver receiver) {
super(receiver);
MatchContext mc = new MatchContext();
mc.bulidContext(root);
matchContext.add(mc);
}
@Override
public void startElement(NodeName elemName, SchemaType typeCode,
int locationId, int properties) throws XPathException {
MatchContext mc = matchContext.getLast();
MatchContext newContext = new MatchContext();
if (mc.elementArcs != null) {
for (PathMapArc arc : mc.elementArcs) {
NodeTest test = arc.getNodeTest();
if (test == null || test.matches(Type.ELEMENT, elemName, typeCode.getFingerprint())) {
newContext.bulidContext(arc.getTarget());
newContext.matchedElement = true;
}
if (arc.getAxis() == AxisInfo.DESCENDANT || arc.getAxis() == AxisInfo.DESCENDANT_OR_SELF) {
newContext.processArc(arc);
}
}
}
matchContext.add(newContext);
if (newContext.matchedElement) {
super.startElement(elemName, typeCode, locationId, properties);
} else if (logTrace) {
LogManager.logTrace(LogConstants.CTX_RUNTIME, "Document projection did not match element", elemName.getURI(), ':', elemName.getLocalPart()); //$NON-NLS-1$
}
}
@Override
public void attribute(NodeName nameCode, SimpleType typeCode,
CharSequence value, int locationId, int properties)
throws XPathException {
MatchContext mc = matchContext.getLast();
if (!mc.matchedElement) {
return;
}
if (nameCode.isInNamespace(javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)) {
super.attribute(nameCode, typeCode, value, locationId, properties);
return;
}
if (mc.attributeArcs != null) {
for (PathMapArc arc : mc.attributeArcs) {
NodeTest test = arc.getNodeTest();
if (test == null || test.matches(Type.ATTRIBUTE, nameCode, typeCode.getFingerprint())) {
super.attribute(nameCode, typeCode, value, locationId, properties);
return;
}
}
}
}
@Override
public void characters(CharSequence chars, int locationId,
int properties) throws XPathException {
MatchContext context = matchContext.getLast();
if (context.matchedElement && context.matchesText) {
super.characters(chars, locationId, properties);
}
}
@Override
public void comment(CharSequence chars, int locationId,
int properties) throws XPathException {
MatchContext context = matchContext.getLast();
if (context.matchedElement && context.matchesComment) {
super.comment(chars, locationId, properties);
}
}
@Override
public void processingInstruction(String target,
CharSequence data, int locationId, int properties)
throws XPathException {
MatchContext context = matchContext.getLast();
if (context.matchedElement) {
super.processingInstruction(target, data, locationId, properties);
}
}
@Override
public void namespace(NamespaceBinding namespaceBinding, int properties)
throws XPathException {
MatchContext context = matchContext.getLast();
if (context.matchedElement) {
super.namespace(namespaceBinding, properties);
}
}
@Override
public void endElement() throws XPathException {
MatchContext context = matchContext.removeLast();
if (context.matchedElement) {
super.endElement();
}
}
@Override
public void startContent() throws XPathException {
MatchContext context = matchContext.getLast();
if (context.matchedElement) {
super.startContent();
}
}
@Override
public void close() throws XPathException {
if (!closed) {
super.close();
closed = true;
}
}
}