Populate xpath/ hierarchy
[yangtools.git] / yang / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / repo / ArgumentContextUtils.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.parser.rfc7950.repo;
9
10 import static com.google.common.base.Verify.verify;
11
12 import com.google.common.annotations.VisibleForTesting;
13 import com.google.common.base.CharMatcher;
14 import java.util.List;
15 import org.eclipse.jdt.annotation.NonNull;
16 import org.opendaylight.yangtools.yang.common.YangVersion;
17 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument;
18 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Concatenation;
19 import org.opendaylight.yangtools.yang.parser.rfc7950.ir.IRArgument.Single;
20 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
21 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
22
23 /**
24  * Utilities for dealing with YANG statement argument strings, encapsulated in ANTLR grammar's ArgumentContext.
25  */
26 abstract class ArgumentContextUtils {
27     /**
28      * YANG 1.0 version of strings, which were not completely clarified in
29      * <a href="https://tools.ietf.org/html/rfc6020#section-6.1.3">RFC6020</a>.
30      */
31     private static final class RFC6020 extends ArgumentContextUtils {
32         private static final @NonNull RFC6020 INSTANCE = new RFC6020();
33
34         @Override
35         void checkDoubleQuoted(final String str, final StatementSourceReference ref, final int backslash) {
36             // No-op
37         }
38
39         @Override
40         void checkUnquoted(final String str, final StatementSourceReference ref) {
41             // No-op
42         }
43     }
44
45     /**
46      * YANG 1.1 version of strings, which were clarified in
47      * <a href="https://tools.ietf.org/html/rfc7950#section-6.1.3">RFC7950</a>.
48      */
49     // NOTE: the differences clarified lead to a proper ability to delegate this to ANTLR lexer, but that does not
50     //       understand versions and needs to work with both.
51     private static final class RFC7950 extends ArgumentContextUtils {
52         private static final CharMatcher ANYQUOTE_MATCHER = CharMatcher.anyOf("'\"");
53         private static final @NonNull RFC7950 INSTANCE = new RFC7950();
54
55         @Override
56         void checkDoubleQuoted(final String str, final StatementSourceReference ref, final int backslash) {
57             if (backslash < str.length() - 1) {
58                 int index = backslash;
59                 while (index != -1) {
60                     switch (str.charAt(index + 1)) {
61                         case 'n':
62                         case 't':
63                         case '\\':
64                         case '\"':
65                             index = str.indexOf('\\', index + 2);
66                             break;
67                         default:
68                             throw new SourceException(ref, "YANG 1.1: illegal double quoted string (%s). In double "
69                                 + "quoted string the backslash must be followed by one of the following character "
70                                 + "[n,t,\",\\], but was '%s'.", str, str.charAt(index + 1));
71                     }
72                 }
73             }
74         }
75
76         @Override
77         void checkUnquoted(final String str, final StatementSourceReference ref) {
78             SourceException.throwIf(ANYQUOTE_MATCHER.matchesAnyOf(str), ref,
79                 "YANG 1.1: unquoted string (%s) contains illegal characters", str);
80         }
81     }
82
83     private ArgumentContextUtils() {
84         // Hidden on purpose
85     }
86
87     static @NonNull ArgumentContextUtils forVersion(final YangVersion version) {
88         switch (version) {
89             case VERSION_1:
90                 return RFC6020.INSTANCE;
91             case VERSION_1_1:
92                 return RFC7950.INSTANCE;
93             default:
94                 throw new IllegalStateException("Unhandled version " + version);
95         }
96     }
97
98     // TODO: teach the only caller about versions, or provide common-enough idioms for its use case
99     static @NonNull ArgumentContextUtils rfc6020() {
100         return RFC6020.INSTANCE;
101     }
102
103     /*
104      * NOTE: this method we do not use convenience methods provided by generated parser code, but instead are making
105      *       based on the grammar assumptions. While this is more verbose, it cuts out a number of unnecessary code,
106      *       such as intermediate List allocation et al.
107      */
108     final @NonNull String stringFromStringContext(final IRArgument argument, final StatementSourceReference ref) {
109         if (argument instanceof Single) {
110             final Single single = (Single) argument;
111             final String str = single.string();
112             if (single.needQuoteCheck()) {
113                 checkUnquoted(str, ref);
114             }
115             return single.needUnescape() ? unescape(str, ref) : str;
116         }
117
118         verify(argument instanceof Concatenation, "Unexpected argument %s", argument);
119         return concatStrings(((Concatenation) argument).parts(), ref);
120     }
121
122     private @NonNull String concatStrings(final List<? extends Single> parts, final StatementSourceReference ref) {
123         final StringBuilder sb = new StringBuilder();
124         for (Single part : parts) {
125             final String str = part.string();
126             sb.append(part.needUnescape() ? unescape(str, ref) : str);
127         }
128         return sb.toString();
129     }
130
131     /*
132      * NOTE: Enforcement and transformation logic done by these methods should logically reside in the lexer and ANTLR
133      *       account the for it with lexer modes. We do not want to force a re-lexing phase in the parser just because
134      *       we decided to let ANTLR do the work.
135      */
136     abstract void checkDoubleQuoted(String str, StatementSourceReference ref, int backslash);
137
138     abstract void checkUnquoted(String str, StatementSourceReference ref);
139
140     private @NonNull String unescape(final String str, final StatementSourceReference ref) {
141         // Now we need to perform some amount of unescaping. This serves as a pre-check before we dispatch
142         // validation and processing (which will reuse the work we have done)
143         final int backslash = str.indexOf('\\');
144         return backslash == -1 ? str : unescape(ref, str, backslash);
145     }
146
147     /*
148      * Unescape escaped double quotes, tabs, new line and backslash in the inner string and trim the result.
149      */
150     private @NonNull String unescape(final StatementSourceReference ref, final String str, final int backslash) {
151         checkDoubleQuoted(str, ref, backslash);
152         StringBuilder sb = new StringBuilder(str.length());
153         unescapeBackslash(sb, str, backslash);
154         return sb.toString();
155     }
156
157     @VisibleForTesting
158     static void unescapeBackslash(final StringBuilder sb, final String str, final int backslash) {
159         String substring = str;
160         int backslashIndex = backslash;
161         while (true) {
162             int nextIndex = backslashIndex + 1;
163             if (backslashIndex != -1 && nextIndex < substring.length()) {
164                 replaceBackslash(sb, substring, nextIndex);
165                 substring = substring.substring(nextIndex + 1);
166                 if (substring.length() > 0) {
167                     backslashIndex = substring.indexOf('\\');
168                 } else {
169                     break;
170                 }
171             } else {
172                 sb.append(substring);
173                 break;
174             }
175         }
176     }
177
178     private static void replaceBackslash(final StringBuilder sb, final String str, final int nextAfterBackslash) {
179         int backslash = nextAfterBackslash - 1;
180         sb.append(str, 0, backslash);
181         final char c = str.charAt(nextAfterBackslash);
182         switch (c) {
183             case '\\':
184             case '"':
185                 sb.append(c);
186                 break;
187             case 't':
188                 sb.append('\t');
189                 break;
190             case 'n':
191                 sb.append('\n');
192                 break;
193             default:
194                 sb.append(str, backslash, nextAfterBackslash + 1);
195         }
196     }
197 }