Allow unqualified node-identifiers in instance-identifier
[yangtools.git] / xpath / yang-xpath-impl / src / main / java / org / opendaylight / yangtools / yang / xpath / impl / InstanceIdentifierParser.java
1 /*
2  * Copyright (c) 2019 Pantheon Technologies, s.r.o.  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.xpath.impl;
9
10 import static java.util.Objects.requireNonNull;
11 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.getChild;
12 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.illegalShape;
13 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.verifyTerminal;
14 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.verifyToken;
15
16 import com.google.common.base.VerifyException;
17 import com.google.common.collect.ImmutableSet;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import javax.xml.xpath.XPathExpressionException;
21 import org.antlr.v4.runtime.CharStreams;
22 import org.antlr.v4.runtime.CommonTokenStream;
23 import org.opendaylight.yangtools.yang.common.UnresolvedQName;
24 import org.opendaylight.yangtools.yang.common.YangNamespaceContext;
25 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierLexer;
26 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser;
27 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.EqQuotedStringContext;
28 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.KeyPredicateContext;
29 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.KeyPredicateExprContext;
30 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.LeafListPredicateContext;
31 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.LeafListPredicateExprContext;
32 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.NodeIdentifierContext;
33 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.PathArgumentContext;
34 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.PosContext;
35 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.PredicateContext;
36 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.QuotedStringContext;
37 import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator;
38 import org.opendaylight.yangtools.yang.xpath.api.YangExpr;
39 import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr;
40 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath;
41 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Absolute;
42 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.QNameStep;
43 import org.opendaylight.yangtools.yang.xpath.api.YangLocationPath.Step;
44 import org.opendaylight.yangtools.yang.xpath.api.YangQNameExpr;
45 import org.opendaylight.yangtools.yang.xpath.api.YangXPathAxis;
46 import org.opendaylight.yangtools.yang.xpath.api.YangXPathMathMode;
47 import org.opendaylight.yangtools.yang.xpath.api.YangXPathMathSupport;
48
49 abstract class InstanceIdentifierParser {
50     static final class Base extends InstanceIdentifierParser {
51         Base(final YangXPathMathMode mathMode) {
52             super(mathMode);
53         }
54
55         @Override
56         YangQNameExpr createExpr(final String localName) {
57             return YangQNameExpr.of(UnresolvedQName.Unqualified.of(localName));
58         }
59
60         @Override
61         YangQNameExpr createExpr(final String prefix, final String localName) {
62             return YangQNameExpr.of(UnresolvedQName.Qualified.of(prefix, localName));
63         }
64
65         @Override
66         QNameStep createChildStep(final String localName, final Collection<YangExpr> predicates) {
67             return YangXPathAxis.CHILD.asStep(UnresolvedQName.Unqualified.of(localName), predicates);
68         }
69
70         @Override
71         QNameStep createChildStep(final String prefix, final String localName, final Collection<YangExpr> predicates) {
72             return YangXPathAxis.CHILD.asStep(UnresolvedQName.Qualified.of(prefix, localName), predicates);
73         }
74     }
75
76     static final class Qualified extends InstanceIdentifierParser {
77         private final YangNamespaceContext namespaceContext;
78
79         Qualified(final YangXPathMathMode mathMode, final YangNamespaceContext namespaceContext) {
80             super(mathMode);
81             this.namespaceContext = requireNonNull(namespaceContext);
82         }
83
84         @Override
85         YangQNameExpr createExpr(final String localName) {
86             return YangQNameExpr.of(UnresolvedQName.Unqualified.of(localName));
87         }
88
89         @Override
90         YangQNameExpr createExpr(final String prefix, final String localName) {
91             return YangQNameExpr.of(namespaceContext.createQName(prefix, localName));
92         }
93
94         @Override
95         QNameStep createChildStep(final String localName, final Collection<YangExpr> predicates) {
96             return YangXPathAxis.CHILD.asStep(UnresolvedQName.Unqualified.of(localName), predicates);
97         }
98
99         @Override
100         QNameStep createChildStep(final String prefix, final String localName, final Collection<YangExpr> predicates) {
101             return YangXPathAxis.CHILD.asStep(namespaceContext.createQName(prefix, localName), predicates);
102         }
103     }
104
105     private final YangXPathMathSupport mathSupport;
106
107     InstanceIdentifierParser(final YangXPathMathMode mathMode) {
108         mathSupport = mathMode.getSupport();
109     }
110
111     final Absolute interpretAsInstanceIdentifier(final YangLiteralExpr expr) throws XPathExpressionException {
112         final var lexer = new instanceIdentifierLexer(CharStreams.fromString(expr.getLiteral()));
113         final var parser = new instanceIdentifierParser(new CommonTokenStream(lexer));
114         final var listener = new CapturingErrorListener();
115         lexer.removeErrorListeners();
116         lexer.addErrorListener(listener);
117         parser.removeErrorListeners();
118         parser.addErrorListener(listener);
119
120         final var id = parser.instanceIdentifier();
121         listener.reportError();
122
123         final int length = id.getChildCount();
124         final var steps = new ArrayList<Step>(length / 2);
125         for (int i = 1; i < length; i += 2) {
126             steps.add(parsePathArgument(getChild(id, PathArgumentContext.class, i)));
127         }
128
129         return YangLocationPath.absolute(steps);
130     }
131
132     abstract YangQNameExpr createExpr(String localName);
133
134     abstract YangQNameExpr createExpr(String prefix, String localName);
135
136     abstract QNameStep createChildStep(String localName, Collection<YangExpr> predicates);
137
138     abstract QNameStep createChildStep(String prefix, String localName, Collection<YangExpr> predicates);
139
140     private QNameStep parsePathArgument(final PathArgumentContext expr) {
141         final var predicates = switch (expr.getChildCount()) {
142             case 1 -> ImmutableSet.<YangExpr>of();
143             case 2 -> parsePredicate(getChild(expr, PredicateContext.class, 1));
144             default -> throw illegalShape(expr);
145         };
146
147         final NodeIdentifierContext childExpr = getChild(expr, NodeIdentifierContext.class, 0);
148         final String first = verifyIdentifier(childExpr, 0);
149
150         return switch (childExpr.getChildCount()) {
151             case 1 -> createChildStep(first, predicates);
152             case 3 -> createChildStep(first, verifyIdentifier(childExpr, 2), predicates);
153             default -> throw illegalShape(childExpr);
154         };
155     }
156
157     private Collection<YangExpr> parsePredicate(final PredicateContext expr) {
158         final var first = expr.getChild(0);
159         if (first instanceof LeafListPredicateContext llp) {
160             return ImmutableSet.of(YangBinaryOperator.EQUALS.exprWith(YangLocationPath.self(),
161                 parseEqStringValue(getChild(llp.getChild(LeafListPredicateExprContext.class, 0),
162                     EqQuotedStringContext.class, 1))));
163         } else if (first instanceof PosContext pc) {
164             return ImmutableSet.of(YangBinaryOperator.EQUALS.exprWith(FunctionSupport.POSITION,
165                 mathSupport.createNumber(pc.getToken(instanceIdentifierParser.PositiveIntegerValue, 0).getText())));
166         }
167
168         final int length = expr.getChildCount();
169         final var ret = new ArrayList<YangExpr>(length);
170         for (int i = 0; i < length; ++i) {
171             final var pred = getChild(expr, KeyPredicateContext.class, i).getChild(KeyPredicateExprContext.class, 0);
172             ret.add(YangBinaryOperator.EQUALS.exprWith(
173                 createChildExpr(getChild(pred, NodeIdentifierContext.class, 0)),
174                 parseEqStringValue(getChild(pred, EqQuotedStringContext.class, 1))));
175         }
176
177         return ret;
178     }
179
180     private YangQNameExpr createChildExpr(final NodeIdentifierContext expr) {
181         final var first = verifyIdentifier(expr, 0);
182         return switch (expr.getChildCount()) {
183             case 1 -> createExpr(first);
184             case 3 -> createExpr(first, verifyIdentifier(expr, 2));
185             default -> throw illegalShape(expr);
186         };
187     }
188
189     private static String verifyIdentifier(final NodeIdentifierContext expr, final int child) {
190         return verifyToken(expr, child, instanceIdentifierParser.Identifier).getText();
191     }
192
193     private static YangLiteralExpr parseEqStringValue(final EqQuotedStringContext expr) {
194         final var quotedString = getChild(expr, QuotedStringContext.class, expr.getChildCount() - 1);
195         return switch (quotedString.getChildCount()) {
196             case 1 -> YangLiteralExpr.empty();
197             case 2 -> {
198                 final var terminal = verifyTerminal(quotedString.getChild(0));
199                 final var token = terminal.getSymbol();
200                 final var literal = switch (token.getType()) {
201                     case instanceIdentifierParser.DQUOT_STRING -> unescape(token.getText());
202                     case instanceIdentifierParser.SQUOT_STRING -> token.getText();
203                     default -> throw illegalShape(terminal);
204                 };
205                 yield YangLiteralExpr.of(literal);
206             }
207             default -> throw illegalShape(quotedString);
208         };
209     }
210
211     private static String unescape(final String escaped) {
212         final int firstEscape = escaped.indexOf('\\');
213         return firstEscape == -1 ? escaped : unescape(escaped, firstEscape);
214     }
215
216     private static String unescape(final String escaped, final int firstEscape) {
217         // Sizing: optimize allocation for only a single escape
218         final int length = escaped.length();
219         final var sb = new StringBuilder(length - 1).append(escaped, 0, firstEscape);
220
221         int esc = firstEscape;
222         while (true) {
223             final var ch = escaped.charAt(esc + 1);
224             sb.append(
225                 switch (ch) {
226                     case 'n' -> '\n';
227                     case 't' -> '\t';
228                     case '"' -> '"';
229                     case '\\' -> '\\';
230                     default -> throw new VerifyException("Unexpected escaped char '" + ch + "'");
231                 });
232
233             final int start = esc + 2;
234             esc = escaped.indexOf('\\', start);
235             if (esc == -1) {
236                 return sb.append(escaped, start, length).toString();
237             }
238
239             sb.append(escaped, start, esc);
240         }
241     }
242 }