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