Do not use jaxen XPath
[yangtools.git] / yang / yang-data-jaxen / src / main / java / org / opendaylight / yangtools / yang / data / jaxen / JaxenXPath.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.yang.data.jaxen;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.Converter;
14 import com.google.common.base.Verify;
15 import com.google.common.collect.Lists;
16 import java.util.AbstractMap.SimpleImmutableEntry;
17 import java.util.List;
18 import java.util.Optional;
19 import javax.annotation.Nonnull;
20 import javax.xml.xpath.XPathExpressionException;
21 import org.jaxen.JaxenException;
22 import org.jaxen.JaxenHandler;
23 import org.jaxen.XPathSyntaxException;
24 import org.jaxen.expr.Expr;
25 import org.jaxen.saxpath.SAXPathException;
26 import org.jaxen.saxpath.XPathReader;
27 import org.opendaylight.yangtools.yang.common.QNameModule;
28 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
29 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathBooleanResult;
30 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathDocument;
31 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathExpression;
32 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathNodesetResult;
33 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathNumberResult;
34 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathResult;
35 import org.opendaylight.yangtools.yang.data.api.schema.xpath.XPathStringResult;
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 final class JaxenXPath implements XPathExpression {
41     private static final Logger LOG = LoggerFactory.getLogger(JaxenXPath.class);
42
43     private final Converter<String, QNameModule> converter;
44     private final SchemaPath schemaPath;
45     private final Expr expr;
46
47     private JaxenXPath(final Converter<String, QNameModule> converter, final SchemaPath schemaPath,
48             final Expr expr) {
49         this.converter = requireNonNull(converter);
50         this.schemaPath = requireNonNull(schemaPath);
51         this.expr = requireNonNull(expr);
52     }
53
54     static JaxenXPath create(final Converter<String, QNameModule> converter, final SchemaPath schemaPath,
55             final String xpath) throws JaxenException {
56
57         final Expr parsed;
58         try {
59             final XPathReader reader = new org.jaxen.saxpath.base.XPathReader();
60             final JaxenHandler handler = new JaxenHandler();
61             reader.setXPathHandler(handler);
62             reader.parse(xpath);
63             parsed = handler.getXPathExpr().getRootExpr();
64         } catch (org.jaxen.saxpath.XPathSyntaxException e) {
65             throw new XPathSyntaxException(e);
66         } catch (SAXPathException e) {
67             throw new JaxenException(e);
68         }
69
70         LOG.debug("Compiled {} to expression {}", xpath, parsed);
71
72         new ExprWalker(new ExprListener() {
73             // FIXME: perform expression introspection to understand things like apex, etc.
74         }).walk(parsed);
75
76         return new JaxenXPath(converter, schemaPath, parsed);
77     }
78
79     @Override
80     public Optional<? extends XPathResult<?>> evaluate(@Nonnull final XPathDocument document,
81             @Nonnull final YangInstanceIdentifier path) throws XPathExpressionException {
82         checkArgument(document instanceof JaxenDocument);
83
84         final NormalizedNodeContextSupport contextSupport = NormalizedNodeContextSupport.create(
85             (JaxenDocument)document, converter);
86
87         final Object result = evaluate(contextSupport.createContext(path));
88         if (result instanceof String) {
89             return Optional.of((XPathStringResult) () -> (String) result);
90         } else if (result instanceof Number) {
91             return Optional.of((XPathNumberResult) () -> (Number) result);
92         } else if (result instanceof Boolean) {
93             return Optional.of((XPathBooleanResult) () -> (Boolean) result);
94         } else if (result == null) {
95             return Optional.empty();
96         }
97
98         Verify.verify(result instanceof List, "Unhandled result %s", result);
99         @SuppressWarnings("unchecked")
100         final List<NormalizedNodeContext> resultList = (List<NormalizedNodeContext>) result;
101         return Optional.of((XPathNodesetResult) () -> {
102             // XXX: Will this really work, or do we need to perform deep transformation?
103             return Lists.transform(resultList,
104                 context -> new SimpleImmutableEntry<>(context.getPath(), context.getNode()));
105         });
106     }
107
108     private Object evaluate(final NormalizedNodeContext context) throws XPathExpressionException {
109         final Object result;
110         try {
111             result = expr.evaluate(context);
112         } catch (JaxenException e) {
113             throw new XPathExpressionException(e);
114         }
115
116         if (result instanceof List) {
117             final List<?> list = (List<?>) result;
118             if (list.size() == 1) {
119                 final Object first = list.get(0);
120                 if (first instanceof String || first instanceof Number || first instanceof Boolean) {
121                     return first;
122                 }
123             }
124         }
125
126         return result;
127     }
128
129     @Nonnull
130     @Override
131     public SchemaPath getEvaluationPath() {
132         return schemaPath;
133     }
134
135     @Nonnull
136     @Override
137     public SchemaPath getApexPath() {
138         // TODO: improve this
139         return SchemaPath.ROOT;
140     }
141 }