Correct instance-identifier escaping
[yangtools.git] / xpath / yang-xpath-impl / src / main / java / org / opendaylight / yangtools / yang / xpath / impl / InstanceIdentifierParser.java
index e232653ac716cf82148325e2a2780c77f1179505..31fac3e9b0f09b85bf41ffa0442bde170b0c375d 100644 (file)
@@ -10,21 +10,21 @@ package org.opendaylight.yangtools.yang.xpath.impl;
 import static java.util.Objects.requireNonNull;
 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.getChild;
 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.illegalShape;
+import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.verifyTerminal;
 import static org.opendaylight.yangtools.yang.xpath.impl.ParseTreeUtils.verifyToken;
 
+import com.google.common.base.VerifyException;
 import com.google.common.collect.ImmutableSet;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import javax.xml.xpath.XPathExpressionException;
 import org.antlr.v4.runtime.CharStreams;
 import org.antlr.v4.runtime.CommonTokenStream;
-import org.antlr.v4.runtime.tree.ParseTree;
 import org.opendaylight.yangtools.yang.common.UnresolvedQName;
 import org.opendaylight.yangtools.yang.common.YangNamespaceContext;
+import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierLexer;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.EqQuotedStringContext;
-import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.InstanceIdentifierContext;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.KeyPredicateContext;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.KeyPredicateExprContext;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.LeafListPredicateContext;
@@ -34,7 +34,6 @@ import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.Path
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.PosContext;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.PredicateContext;
 import org.opendaylight.yangtools.yang.xpath.antlr.instanceIdentifierParser.QuotedStringContext;
-import org.opendaylight.yangtools.yang.xpath.antlr.xpathLexer;
 import org.opendaylight.yangtools.yang.xpath.api.YangBinaryOperator;
 import org.opendaylight.yangtools.yang.xpath.api.YangExpr;
 import org.opendaylight.yangtools.yang.xpath.api.YangLiteralExpr;
@@ -92,19 +91,19 @@ abstract class InstanceIdentifierParser {
     }
 
     final Absolute interpretAsInstanceIdentifier(final YangLiteralExpr expr) throws XPathExpressionException {
-        final xpathLexer lexer = new xpathLexer(CharStreams.fromString(expr.getLiteral()));
-        final instanceIdentifierParser parser = new instanceIdentifierParser(new CommonTokenStream(lexer));
-        final CapturingErrorListener listener = new CapturingErrorListener();
+        final var lexer = new instanceIdentifierLexer(CharStreams.fromString(expr.getLiteral()));
+        final var parser = new instanceIdentifierParser(new CommonTokenStream(lexer));
+        final var listener = new CapturingErrorListener();
         lexer.removeErrorListeners();
         lexer.addErrorListener(listener);
         parser.removeErrorListeners();
         parser.addErrorListener(listener);
 
-        final InstanceIdentifierContext id = parser.instanceIdentifier();
+        final var id = parser.instanceIdentifier();
         listener.reportError();
 
         final int length = id.getChildCount();
-        final List<Step> steps = new ArrayList<>(length / 2);
+        final var steps = new ArrayList<Step>(length / 2);
         for (int i = 1; i < length; i += 2) {
             steps.add(parsePathArgument(getChild(id, PathArgumentContext.class, i)));
         }
@@ -121,37 +120,31 @@ abstract class InstanceIdentifierParser {
         final String prefix = verifyIdentifier(childExpr, 0);
         final String localName = verifyIdentifier(childExpr, 2);
 
-        switch (expr.getChildCount()) {
-            case 1:
-                return createChildStep(prefix, localName, ImmutableSet.of());
-            case 2:
-                return createChildStep(prefix, localName, parsePredicate(getChild(expr, PredicateContext.class, 1)));
-            default:
-                throw illegalShape(expr);
-        }
+        return switch (expr.getChildCount()) {
+            case 1 -> createChildStep(prefix, localName, ImmutableSet.of());
+            case 2 -> createChildStep(prefix, localName, parsePredicate(getChild(expr, PredicateContext.class, 1)));
+            default -> throw illegalShape(expr);
+        };
     }
 
     private Collection<YangExpr> parsePredicate(final PredicateContext expr) {
-        final ParseTree first = expr.getChild(0);
-        if (first instanceof LeafListPredicateContext) {
+        final var first = expr.getChild(0);
+        if (first instanceof LeafListPredicateContext llp) {
             return ImmutableSet.of(YangBinaryOperator.EQUALS.exprWith(YangLocationPath.self(),
-                parseEqStringValue(getChild(((LeafListPredicateContext) first)
-                    .getChild(LeafListPredicateExprContext.class, 0), EqQuotedStringContext.class, 1))));
-        } else if (first instanceof PosContext) {
+                parseEqStringValue(getChild(llp.getChild(LeafListPredicateExprContext.class, 0),
+                    EqQuotedStringContext.class, 1))));
+        } else if (first instanceof PosContext pc) {
             return ImmutableSet.of(YangBinaryOperator.EQUALS.exprWith(FunctionSupport.POSITION,
-                mathSupport.createNumber(((PosContext) first).getToken(instanceIdentifierParser.PositiveIntegerValue, 0)
-                    .getText())));
+                mathSupport.createNumber(pc.getToken(instanceIdentifierParser.PositiveIntegerValue, 0).getText())));
         }
 
         final int length = expr.getChildCount();
-        final List<YangExpr> ret = new ArrayList<>(length);
+        final var ret = new ArrayList<YangExpr>(length);
         for (int i = 0; i < length; ++i) {
-            final KeyPredicateExprContext pred = getChild(expr, KeyPredicateContext.class, i)
-                    .getChild(KeyPredicateExprContext.class, 0);
+            final var pred = getChild(expr, KeyPredicateContext.class, i).getChild(KeyPredicateExprContext.class, 0);
             ret.add(YangBinaryOperator.EQUALS.exprWith(
                 createChildExpr(getChild(pred, NodeIdentifierContext.class, 0)),
                 parseEqStringValue(getChild(pred, EqQuotedStringContext.class, 1))));
-
         }
 
         return ret;
@@ -166,7 +159,52 @@ abstract class InstanceIdentifierParser {
     }
 
     private static YangLiteralExpr parseEqStringValue(final EqQuotedStringContext expr) {
-        return YangLiteralExpr.of(verifyToken(getChild(expr, QuotedStringContext.class, expr.getChildCount() - 1), 1,
-            instanceIdentifierParser.STRING).getText());
+        final var quotedString = getChild(expr, QuotedStringContext.class, expr.getChildCount() - 1);
+        return switch (quotedString.getChildCount()) {
+            case 1 -> YangLiteralExpr.empty();
+            case 2 -> {
+                final var terminal = verifyTerminal(quotedString.getChild(0));
+                final var token = terminal.getSymbol();
+                final var literal = switch (token.getType()) {
+                    case instanceIdentifierParser.DQUOT_STRING -> unescape(token.getText());
+                    case instanceIdentifierParser.SQUOT_STRING -> token.getText();
+                    default -> throw illegalShape(terminal);
+                };
+                yield YangLiteralExpr.of(literal);
+            }
+            default -> throw illegalShape(quotedString);
+        };
+    }
+
+    private static String unescape(final String escaped) {
+        final int firstEscape = escaped.indexOf('\\');
+        return firstEscape == -1 ? escaped : unescape(escaped, firstEscape);
+    }
+
+    private static String unescape(final String escaped, final int firstEscape) {
+        // Sizing: optimize allocation for only a single escape
+        final int length = escaped.length();
+        final var sb = new StringBuilder(length - 1).append(escaped, 0, firstEscape);
+
+        int esc = firstEscape;
+        while (true) {
+            final var ch = escaped.charAt(esc + 1);
+            sb.append(
+                switch (ch) {
+                    case 'n' -> '\n';
+                    case 't' -> '\t';
+                    case '"' -> '"';
+                    case '\\' -> '\\';
+                    default -> throw new VerifyException("Unexpected escaped char '" + ch + "'");
+                });
+
+            final int start = esc + 2;
+            esc = escaped.indexOf('\\', start);
+            if (esc == -1) {
+                return sb.append(escaped, start, length).toString();
+            }
+
+            sb.append(escaped, start, esc);
+        }
     }
 }