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