Bug 3670 (part 1/5): Use of new statement parser in yang-maven-plugin
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / Utils.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.stmt.rfc6020;
9
10 import static org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils.firstAttributeOf;
11
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Splitter;
14 import com.google.common.collect.Iterables;
15
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collection;
19 import java.util.HashSet;
20 import java.util.Iterator;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Objects;
24 import java.util.Set;
25
26 import javax.annotation.Nullable;
27 import javax.xml.xpath.XPath;
28 import javax.xml.xpath.XPathExpressionException;
29 import javax.xml.xpath.XPathFactory;
30
31 import org.antlr.v4.runtime.tree.TerminalNode;
32 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
33 import org.opendaylight.yangtools.yang.common.QName;
34 import org.opendaylight.yangtools.yang.common.QNameModule;
35 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
36 import org.opendaylight.yangtools.yang.common.YangConstants;
37 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.Deviation;
39 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
40 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
41 import org.opendaylight.yangtools.yang.model.api.Status;
42 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
43 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
44 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceStatement;
45 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
46 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
47 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
48 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
50 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
51 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext.Mutable;
52 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
53 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
54 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
55 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
56 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
57 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
58 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
59 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
60 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
61 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
62 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
63 import org.slf4j.Logger;
64 import org.slf4j.LoggerFactory;
65
66 public final class Utils {
67
68     private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
69     private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
70     private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
71
72     public static final QName EMPTY_QNAME = QName.create("empty", "empty");
73
74     private static final char SEPARATOR_NODENAME = '/';
75
76     private static final String REGEX_PATH_ABS = "/[^/].*";
77
78     public static final char SEPARATOR = ' ';
79
80     private Utils() {
81     }
82
83     public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(StmtContext<?, ?, ?> ctx,
84             String value) {
85         Splitter keySplitter = Splitter.on(SEPARATOR).omitEmptyStrings().trimResults();
86         List<String> keyTokens = keySplitter.splitToList(value);
87
88         // to detect if key contains duplicates
89         if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
90             throw new IllegalArgumentException();
91         }
92
93         Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
94
95         for (String keyToken : keyTokens) {
96
97             SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
98                     Utils.qNameFromArgument(ctx, keyToken));
99             keyNodes.add(keyNode);
100         }
101
102         return keyNodes;
103     }
104
105     public static List<String> splitPathToNodeNames(String path) {
106
107         Splitter keySplitter = Splitter.on(SEPARATOR_NODENAME).omitEmptyStrings().trimResults();
108         return keySplitter.splitToList(path);
109     }
110
111     public static void validateXPath(final StmtContext<?, ?, ?> ctx, String path) {
112
113         final XPath xPath = XPathFactory.newInstance().newXPath();
114
115         try {
116             xPath.compile(path);
117         } catch (XPathExpressionException e) {
118             throw new IllegalArgumentException(String.format("Argument %s is not valid XPath string at %s", path, ctx
119                     .getStatementSourceReference().toString()), e);
120         }
121     }
122
123     private static String trimSingleLastSlashFromXPath(final String path) {
124         return path.replaceAll("/$", "");
125     }
126
127     public static boolean isXPathAbsolute(final StmtContext<?, ?, ?> ctx, String path) {
128
129         validateXPath(ctx, trimSingleLastSlashFromXPath(path));
130
131         return path.matches(REGEX_PATH_ABS);
132     }
133
134     public static QName trimPrefix(QName identifier) {
135         String prefixedLocalName = identifier.getLocalName();
136         String[] namesParts = prefixedLocalName.split(":");
137
138         if (namesParts.length == 2) {
139             String localName = namesParts[1];
140             return QName.create(identifier.getModule(), localName);
141         }
142
143         return identifier;
144     }
145
146     public static String getPrefixFromArgument(String prefixedLocalName) {
147         String[] namesParts = prefixedLocalName.split(":");
148         if (namesParts.length == 2) {
149             return namesParts[0];
150         }
151         return null;
152     }
153
154     public static boolean isValidStatementDefinition(PrefixToModule prefixes, QNameToStatementDefinition stmtDef,
155             QName identifier) {
156         if (stmtDef.get(identifier) != null) {
157             return true;
158         } else {
159             String prefixedLocalName = identifier.getLocalName();
160             String[] namesParts = prefixedLocalName.split(":");
161
162             if (namesParts.length == 2) {
163                 String prefix = namesParts[0];
164                 String localName = namesParts[1];
165                 if (prefixes != null && prefixes.get(prefix) != null
166                         && stmtDef.get(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
167                     return true;
168                 } else {
169                     if (stmtDef.get(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
170                         return true;
171                     }
172                 }
173             }
174         }
175         return false;
176     }
177
178     public static Iterable<QName> parseXPath(StmtContext<?, ?, ?> ctx, String path) {
179
180         String trimmedPath = trimSingleLastSlashFromXPath(path);
181
182         validateXPath(ctx, trimmedPath);
183
184         List<String> nodeNames = splitPathToNodeNames(trimmedPath);
185         List<QName> qNames = new ArrayList<>();
186
187         for (String nodeName : nodeNames) {
188             try {
189                 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
190                 qNames.add(qName);
191             } catch (Exception e) {
192                 throw new IllegalArgumentException(e);
193             }
194         }
195
196         return qNames;
197     }
198
199     public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
200         StringBuilder sb = new StringBuilder();
201         List<TerminalNode> strings = context.STRING();
202         if (strings.isEmpty()) {
203             strings = Arrays.asList(context.IDENTIFIER());
204         }
205         for (TerminalNode stringNode : strings) {
206             final String str = stringNode.getText();
207             char firstChar = str.charAt(0);
208             final CharMatcher quoteMatcher;
209             if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
210                 quoteMatcher = SINGLE_QUOTE_MATCHER;
211             } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
212                 quoteMatcher = DOUBLE_QUOTE_MATCHER;
213             } else {
214                 sb.append(str);
215                 continue;
216             }
217             sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
218         }
219         return sb.toString();
220     }
221
222     public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, String value) {
223
224         if (value == null || value.equals("")) {
225             return EMPTY_QNAME;
226         }
227
228         String prefix;
229         QNameModule qNameModule = null;
230         String localName = null;
231
232         String[] namesParts = value.split(":");
233         switch (namesParts.length) {
234         case 1:
235             localName = namesParts[0];
236             qNameModule = getRootModuleQName(ctx);
237             break;
238         case 2:
239             prefix = namesParts[0];
240             localName = namesParts[1];
241             qNameModule = getModuleQNameByPrefix(ctx, prefix);
242             // in case of unknown statement argument, we're not going to parse it
243             if (qNameModule == null
244                     && ctx.getPublicDefinition().getDeclaredRepresentationClass()
245                     .isAssignableFrom(UnknownStatementImpl.class)) {
246                 localName = value;
247                 qNameModule = getRootModuleQName(ctx);
248             }
249             if (qNameModule == null
250                     && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
251                 ctx = ctx.getOriginalCtx();
252                 qNameModule = getModuleQNameByPrefix(ctx, prefix);
253             }
254             break;
255         default:
256             break;
257         }
258
259         if (qNameModule == null) {
260             throw new IllegalArgumentException("Error in module '" + ctx.getRoot().rawStatementArgument()
261                     + "': can not resolve QNameModule for '" + value + "'.");
262         }
263
264         QNameModule resultQNameModule = qNameModule.getRevision() == null ? QNameModule.create(
265                 qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
266
267         return QName.create(resultQNameModule, localName);
268     }
269
270     public static QNameModule getModuleQNameByPrefix(StmtContext<?, ?, ?> ctx, String prefix) {
271         QNameModule qNameModule;
272         ModuleIdentifier impModIdentifier = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
273         qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, impModIdentifier);
274
275         if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
276             String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
277             qNameModule = ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
278         }
279         return qNameModule;
280     }
281
282     public static QNameModule getRootModuleQName(StmtContext<?, ?, ?> ctx) {
283
284         if (ctx == null) {
285             return null;
286         }
287
288         StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
289         QNameModule qNameModule = null;
290
291         if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
292             qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
293         } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
294             String belongsToModuleName = firstAttributeOf(ctx.getRoot().declaredSubstatements(),
295                     BelongsToStatement.class);
296             qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
297         }
298
299         return qNameModule.getRevision() == null ? QNameModule.create(qNameModule.getNamespace(),
300                 SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
301     }
302
303     @Nullable
304     public static StatementContextBase<?, ?, ?> findNode(StatementContextBase<?, ?, ?> rootStmtCtx,
305             final Iterable<QName> path) {
306
307         StatementContextBase<?, ?, ?> parent = rootStmtCtx;
308
309         Iterator<QName> pathIter = path.iterator();
310         while (pathIter.hasNext()) {
311             QName nextPathQName = pathIter.next();
312             StatementContextBase<?, ?, ?> foundSubstatement = getSubstatementByQName(parent, nextPathQName);
313
314             if (foundSubstatement == null) {
315                 return null;
316             }
317             if (!pathIter.hasNext()) {
318                 return foundSubstatement;
319             }
320
321             parent = foundSubstatement;
322         }
323
324         return null;
325     }
326
327     public static StatementContextBase<?, ?, ?> getSubstatementByQName(StatementContextBase<?, ?, ?> parent,
328             QName nextPathQName) {
329
330         Collection<StatementContextBase<?, ?, ?>> declaredSubstatement = parent.declaredSubstatements();
331         Collection<StatementContextBase<?, ?, ?>> effectiveSubstatement = parent.effectiveSubstatements();
332
333         Collection<StatementContextBase<?, ?, ?>> allSubstatements = new LinkedList<>();
334         allSubstatements.addAll(declaredSubstatement);
335         allSubstatements.addAll(effectiveSubstatement);
336
337         for (StatementContextBase<?, ?, ?> substatement : allSubstatements) {
338             if (nextPathQName.equals(substatement.getStatementArgument())) {
339                 return substatement;
340             }
341         }
342
343         return null;
344     }
345
346     @Nullable
347     public static StatementContextBase<?, ?, ?> findNode(StatementContextBase<?, ?, ?> rootStmtCtx,
348             final SchemaNodeIdentifier node) {
349         return findNode(rootStmtCtx, node.getPathFromRoot());
350     }
351
352     public static SchemaPath getSchemaPath(StmtContext<?, ?, ?> ctx) {
353
354         if (ctx == null) {
355             return null;
356         }
357
358         Iterator<StmtContext<?, ?, ?>> iteratorFromRoot = ctx.getStmtContextsFromRoot().iterator();
359
360         if (iteratorFromRoot.hasNext()) {
361             iteratorFromRoot.next(); // skip root argument
362         }
363
364         List<QName> qNamesFromRoot = new LinkedList<>();
365         while (iteratorFromRoot.hasNext()) {
366             StmtContext<?, ?, ?> nextStmtCtx = iteratorFromRoot.next();
367             Object nextStmtArgument = nextStmtCtx.getStatementArgument();
368             if (nextStmtArgument instanceof QName) {
369                 QName qname = (QName) nextStmtArgument;
370                 if (StmtContextUtils.producesDeclared(nextStmtCtx, UsesStatement.class)) {
371                     continue;
372                 }
373                 if (StmtContextUtils.producesDeclared(nextStmtCtx.getParentContext(), ChoiceStatement.class)
374                         && isSupportedAsShorthandCase(nextStmtCtx)) {
375                     qNamesFromRoot.add(qname);
376                 }
377                 qNamesFromRoot.add(qname);
378             } else if (nextStmtArgument instanceof String) {
379                 final QName qName = qNameFromArgument(ctx, (String) nextStmtArgument);
380                 qNamesFromRoot.add(qName);
381             } else if (StmtContextUtils.producesDeclared(nextStmtCtx, AugmentStatement.class)
382                     && nextStmtArgument instanceof SchemaNodeIdentifier) {
383                 addQNamesFromSchemaNodeIdentifierToList(qNamesFromRoot, (SchemaNodeIdentifier) nextStmtArgument);
384             } else if (nextStmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
385                     .isAssignableFrom(UnknownStatementImpl.class)) {
386                 qNamesFromRoot.add(nextStmtCtx.getPublicDefinition().getStatementName());
387             } else {
388                 return SchemaPath.SAME;
389             }
390         }
391
392         final SchemaPath schemaPath = SchemaPath.create(qNamesFromRoot, true);
393         return schemaPath;
394     }
395
396     private static boolean isSupportedAsShorthandCase(StmtContext<?, ?, ?> statementCtx) {
397
398         Collection<?> supportedCaseShorthands = statementCtx.getFromNamespace(ValidationBundlesNamespace.class,
399                 ValidationBundleType.SUPPORTED_CASE_SHORTHANDS);
400
401         return supportedCaseShorthands == null || supportedCaseShorthands.contains(statementCtx.getPublicDefinition());
402     }
403
404     private static void addQNamesFromSchemaNodeIdentifierToList(List<QName> qNamesFromRoot,
405             SchemaNodeIdentifier augmentTargetPath) {
406         Iterator<QName> augmentTargetPathIterator = augmentTargetPath.getPathFromRoot().iterator();
407         while (augmentTargetPathIterator.hasNext()) {
408             qNamesFromRoot.add(augmentTargetPathIterator.next());
409         }
410     }
411
412     public static Deviation.Deviate parseDeviateFromString(final String deviate) {
413
414         // Yang constants should be lowercase so we have throw if value does not
415         // suit this
416         String deviateUpper = deviate.toUpperCase();
417         if (Objects.equals(deviate, deviateUpper)) {
418             throw new IllegalArgumentException(String.format("String %s is not valid deviate argument", deviate));
419         }
420
421         // but Java enum is uppercase so we cannot use lowercase here
422         try {
423             return Deviation.Deviate.valueOf(deviateUpper);
424         } catch (IllegalArgumentException e) {
425             throw new IllegalArgumentException(String.format("String %s is not valid deviate argument", deviate), e);
426         }
427     }
428
429     public static Status parseStatus(String value) {
430
431         Status status = null;
432         switch (value) {
433         case "current":
434             status = Status.CURRENT;
435             break;
436         case "deprecated":
437             status = Status.DEPRECATED;
438             break;
439         case "obsolete":
440             status = Status.OBSOLETE;
441             break;
442         default:
443             LOG.warn("Invalid 'status' statement: " + value);
444         }
445
446         return status;
447     }
448
449     public static SchemaPath SchemaNodeIdentifierToSchemaPath(SchemaNodeIdentifier identifier) {
450         return SchemaPath.create(identifier.getPathFromRoot(), identifier.isAbsolute());
451     }
452
453 }