BUG-3876: Add jaxen-based XPath evaluation component
[yangtools.git] / yang / yang-data-jaxen / src / main / java / org / opendaylight / yangtools / yang / data / jaxen / JaxenXPath.java
diff --git a/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenXPath.java b/yang/yang-data-jaxen/src/main/java/org/opendaylight/yangtools/yang/data/jaxen/JaxenXPath.java
new file mode 100644 (file)
index 0000000..563b695
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.jaxen;
+
+import com.google.common.base.Converter;
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import java.util.Collection;
+import java.util.List;
+import javax.xml.xpath.XPathExpressionException;
+import org.jaxen.BaseXPath;
+import org.jaxen.ContextSupport;
+import org.jaxen.JaxenException;
+import org.jaxen.expr.Expr;
+import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathBooleanResult;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathDocument;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathExpression;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathNodesetResult;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathNumberResult;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathResult;
+import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathStringResult;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class JaxenXPath implements XPathExpression {
+    private static final Logger LOG = LoggerFactory.getLogger(JaxenXPath.class);
+    protected static final Function<NormalizedNodeContext, NormalizedNode<?, ?>> EXTRACT_NODE =
+            new Function<NormalizedNodeContext, NormalizedNode<?, ?>>() {
+        @Override
+        public NormalizedNode<?, ?> apply(final NormalizedNodeContext input) {
+            return input.getNode();
+        }
+    };
+    private final Converter<String, QNameModule> converter;
+    private final SchemaPath schemaPath;
+    private final BaseXPath xpath;
+
+    private JaxenXPath(final Converter<String, QNameModule> converter, final SchemaPath schemaPath,
+            final BaseXPath xpath) {
+        this.converter = Preconditions.checkNotNull(converter);
+        this.schemaPath = Preconditions.checkNotNull(schemaPath);
+        this.xpath = Preconditions.checkNotNull(xpath);
+    }
+
+    static JaxenXPath create(final Converter<String, QNameModule> converter, final SchemaPath schemaPath,
+            final String xpath) throws JaxenException {
+        final BaseXPath compiled = new BaseXPath(xpath) {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected ContextSupport getContextSupport() {
+                throw new UnsupportedOperationException(xpath);
+            }
+        };
+
+        final Expr expr = compiled.getRootExpr();
+        LOG.debug("Compiled {} to expression {}", xpath, expr);
+
+        // FIXME: perform expression introspection to understand things like apex, etc.
+
+        return new JaxenXPath(converter, schemaPath, compiled);
+    }
+
+    @Override
+    public Optional<? extends XPathResult<?>> evaluate(final XPathDocument document, final YangInstanceIdentifier path)
+            throws XPathExpressionException {
+        Preconditions.checkArgument(document instanceof JaxenDocument);
+
+        final NormalizedNodeContextSupport contextSupport = NormalizedNodeContextSupport.create(
+            (JaxenDocument)document, converter);
+
+        final Object result;
+        try {
+            result = xpath.evaluate(contextSupport.createContext(path));
+        } catch (JaxenException e) {
+            throw new XPathExpressionException(e);
+        }
+
+        if (result instanceof String) {
+            return Optional.of(new XPathStringResult() {
+                @Override
+                public String getValue() {
+                    return (String)result;
+                }
+            });
+        } else if (result instanceof Number) {
+            return Optional.of(new XPathNumberResult() {
+                @Override
+                public Number getValue() {
+                    return (Number) result;
+                }
+            });
+        } else if (result instanceof Boolean) {
+            return Optional.of(new XPathBooleanResult() {
+                @Override
+                public Boolean getValue() {
+                    return (Boolean) result;
+                }
+            });
+        } else if (result != null){
+            return Optional.of(new XPathNodesetResult() {
+                @SuppressWarnings("unchecked")
+                @Override
+                public Collection<NormalizedNode<?, ?>> getValue() {
+                    // XXX: Will this really work, or do we need to perform deep transformation?
+                    return Lists.transform((List<NormalizedNodeContext>) result, EXTRACT_NODE);
+                }
+            });
+        } else {
+            return Optional.absent();
+        }
+    }
+
+    @Override
+    public SchemaPath getEvaluationPath() {
+        return schemaPath;
+    }
+
+    @Override
+    public SchemaPath getApexPath() {
+        // TODO: improve this
+        return SchemaPath.ROOT;
+    }
+}