2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.parser.stmt.rfc6020;
10 import static org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils.firstAttributeOf;
12 import com.google.common.base.CharMatcher;
13 import com.google.common.base.Splitter;
14 import com.google.common.collect.Iterables;
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;
26 import javax.annotation.Nullable;
27 import javax.xml.xpath.XPath;
28 import javax.xml.xpath.XPathExpressionException;
29 import javax.xml.xpath.XPathFactory;
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;
66 public final class Utils {
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('\'');
72 public static final QName EMPTY_QNAME = QName.create("empty", "empty");
74 private static final char SEPARATOR_NODENAME = '/';
76 private static final String REGEX_PATH_ABS = "/[^/].*";
78 public static final char SEPARATOR = ' ';
83 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(StmtContext<?, ?, ?> ctx,
85 Splitter keySplitter = Splitter.on(SEPARATOR).omitEmptyStrings().trimResults();
86 List<String> keyTokens = keySplitter.splitToList(value);
88 // to detect if key contains duplicates
89 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
90 throw new IllegalArgumentException();
93 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
95 for (String keyToken : keyTokens) {
97 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
98 Utils.qNameFromArgument(ctx, keyToken));
99 keyNodes.add(keyNode);
105 public static List<String> splitPathToNodeNames(String path) {
107 Splitter keySplitter = Splitter.on(SEPARATOR_NODENAME).omitEmptyStrings().trimResults();
108 return keySplitter.splitToList(path);
111 public static void validateXPath(final StmtContext<?, ?, ?> ctx, String path) {
113 final XPath xPath = XPathFactory.newInstance().newXPath();
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);
123 private static String trimSingleLastSlashFromXPath(final String path) {
124 return path.replaceAll("/$", "");
127 public static boolean isXPathAbsolute(final StmtContext<?, ?, ?> ctx, String path) {
129 validateXPath(ctx, trimSingleLastSlashFromXPath(path));
131 return path.matches(REGEX_PATH_ABS);
134 public static QName trimPrefix(QName identifier) {
135 String prefixedLocalName = identifier.getLocalName();
136 String[] namesParts = prefixedLocalName.split(":");
138 if (namesParts.length == 2) {
139 String localName = namesParts[1];
140 return QName.create(identifier.getModule(), localName);
146 public static String getPrefixFromArgument(String prefixedLocalName) {
147 String[] namesParts = prefixedLocalName.split(":");
148 if (namesParts.length == 2) {
149 return namesParts[0];
154 public static boolean isValidStatementDefinition(PrefixToModule prefixes, QNameToStatementDefinition stmtDef,
156 if (stmtDef.get(identifier) != null) {
159 String prefixedLocalName = identifier.getLocalName();
160 String[] namesParts = prefixedLocalName.split(":");
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) {
169 if (stmtDef.get(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
178 public static Iterable<QName> parseXPath(StmtContext<?, ?, ?> ctx, String path) {
180 String trimmedPath = trimSingleLastSlashFromXPath(path);
182 validateXPath(ctx, trimmedPath);
184 List<String> nodeNames = splitPathToNodeNames(trimmedPath);
185 List<QName> qNames = new ArrayList<>();
187 for (String nodeName : nodeNames) {
189 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
191 } catch (Exception e) {
192 throw new IllegalArgumentException(e);
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());
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;
217 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
219 return sb.toString();
222 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, String value) {
224 if (value == null || value.equals("")) {
229 QNameModule qNameModule = null;
230 String localName = null;
232 String[] namesParts = value.split(":");
233 switch (namesParts.length) {
235 localName = namesParts[0];
236 qNameModule = getRootModuleQName(ctx);
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)) {
247 qNameModule = getRootModuleQName(ctx);
249 if (qNameModule == null
250 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
251 ctx = ctx.getOriginalCtx();
252 qNameModule = getModuleQNameByPrefix(ctx, prefix);
259 if (qNameModule == null) {
260 throw new IllegalArgumentException("Error in module '" + ctx.getRoot().rawStatementArgument()
261 + "': can not resolve QNameModule for '" + value + "'.");
264 QNameModule resultQNameModule = qNameModule.getRevision() == null ? QNameModule.create(
265 qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
267 return QName.create(resultQNameModule, localName);
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);
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);
282 public static QNameModule getRootModuleQName(StmtContext<?, ?, ?> ctx) {
288 StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
289 QNameModule qNameModule = null;
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);
299 return qNameModule.getRevision() == null ? QNameModule.create(qNameModule.getNamespace(),
300 SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
304 public static StatementContextBase<?, ?, ?> findNode(StatementContextBase<?, ?, ?> rootStmtCtx,
305 final Iterable<QName> path) {
307 StatementContextBase<?, ?, ?> parent = rootStmtCtx;
309 Iterator<QName> pathIter = path.iterator();
310 while (pathIter.hasNext()) {
311 QName nextPathQName = pathIter.next();
312 StatementContextBase<?, ?, ?> foundSubstatement = getSubstatementByQName(parent, nextPathQName);
314 if (foundSubstatement == null) {
317 if (!pathIter.hasNext()) {
318 return foundSubstatement;
321 parent = foundSubstatement;
327 public static StatementContextBase<?, ?, ?> getSubstatementByQName(StatementContextBase<?, ?, ?> parent,
328 QName nextPathQName) {
330 Collection<StatementContextBase<?, ?, ?>> declaredSubstatement = parent.declaredSubstatements();
331 Collection<StatementContextBase<?, ?, ?>> effectiveSubstatement = parent.effectiveSubstatements();
333 Collection<StatementContextBase<?, ?, ?>> allSubstatements = new LinkedList<>();
334 allSubstatements.addAll(declaredSubstatement);
335 allSubstatements.addAll(effectiveSubstatement);
337 for (StatementContextBase<?, ?, ?> substatement : allSubstatements) {
338 if (nextPathQName.equals(substatement.getStatementArgument())) {
347 public static StatementContextBase<?, ?, ?> findNode(StatementContextBase<?, ?, ?> rootStmtCtx,
348 final SchemaNodeIdentifier node) {
349 return findNode(rootStmtCtx, node.getPathFromRoot());
352 public static SchemaPath getSchemaPath(StmtContext<?, ?, ?> ctx) {
358 Iterator<StmtContext<?, ?, ?>> iteratorFromRoot = ctx.getStmtContextsFromRoot().iterator();
360 if (iteratorFromRoot.hasNext()) {
361 iteratorFromRoot.next(); // skip root argument
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)) {
373 if (StmtContextUtils.producesDeclared(nextStmtCtx.getParentContext(), ChoiceStatement.class)
374 && isSupportedAsShorthandCase(nextStmtCtx)) {
375 qNamesFromRoot.add(qname);
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());
388 return SchemaPath.SAME;
392 final SchemaPath schemaPath = SchemaPath.create(qNamesFromRoot, true);
396 private static boolean isSupportedAsShorthandCase(StmtContext<?, ?, ?> statementCtx) {
398 Collection<?> supportedCaseShorthands = statementCtx.getFromNamespace(ValidationBundlesNamespace.class,
399 ValidationBundleType.SUPPORTED_CASE_SHORTHANDS);
401 return supportedCaseShorthands == null || supportedCaseShorthands.contains(statementCtx.getPublicDefinition());
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());
412 public static Deviation.Deviate parseDeviateFromString(final String deviate) {
414 // Yang constants should be lowercase so we have throw if value does not
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));
421 // but Java enum is uppercase so we cannot use lowercase here
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);
429 public static Status parseStatus(String value) {
431 Status status = null;
434 status = Status.CURRENT;
437 status = Status.DEPRECATED;
440 status = Status.OBSOLETE;
443 LOG.warn("Invalid 'status' statement: " + value);
449 public static SchemaPath SchemaNodeIdentifierToSchemaPath(SchemaNodeIdentifier identifier) {
450 return SchemaPath.create(identifier.getPathFromRoot(), identifier.isAbsolute());