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;
11 import com.google.common.base.CharMatcher;
12 import com.google.common.base.Splitter;
13 import com.google.common.collect.Iterables;
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Date;
18 import java.util.HashSet;
19 import java.util.Iterator;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Objects;
24 import javax.annotation.Nullable;
25 import javax.xml.xpath.XPath;
26 import javax.xml.xpath.XPathExpressionException;
27 import javax.xml.xpath.XPathFactory;
28 import org.antlr.v4.runtime.tree.TerminalNode;
29 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
30 import org.opendaylight.yangtools.yang.common.QName;
31 import org.opendaylight.yangtools.yang.common.QNameModule;
32 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
33 import org.opendaylight.yangtools.yang.common.YangConstants;
34 import org.opendaylight.yangtools.yang.model.api.Deviation;
35 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
36 import org.opendaylight.yangtools.yang.model.api.SchemaPath;
37 import org.opendaylight.yangtools.yang.model.api.Status;
38 import org.opendaylight.yangtools.yang.model.api.stmt.AugmentStatement;
39 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
40 import org.opendaylight.yangtools.yang.model.api.stmt.ChoiceStatement;
41 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
42 import org.opendaylight.yangtools.yang.model.api.stmt.RefineStatement;
43 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
44 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
45 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
46 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
47 import org.opendaylight.yangtools.yang.model.api.stmt.UsesStatement;
48 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
49 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
50 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
51 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
52 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
53 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
54 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
55 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
56 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
57 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace;
58 import org.opendaylight.yangtools.yang.parser.spi.validation.ValidationBundlesNamespace.ValidationBundleType;
59 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
60 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 public final class Utils {
66 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
67 private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
68 private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
70 private static final char SEPARATOR_NODENAME = '/';
72 private static final String REGEX_PATH_ABS = "/[^/].*";
74 public static final char SEPARATOR = ' ';
79 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
81 Splitter keySplitter = Splitter.on(SEPARATOR).omitEmptyStrings().trimResults();
82 List<String> keyTokens = keySplitter.splitToList(value);
84 // to detect if key contains duplicates
85 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
86 throw new IllegalArgumentException();
89 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
91 for (String keyToken : keyTokens) {
93 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
94 Utils.qNameFromArgument(ctx, keyToken));
95 keyNodes.add(keyNode);
101 public static List<String> splitPathToNodeNames(final String path) {
103 Splitter keySplitter = Splitter.on(SEPARATOR_NODENAME).omitEmptyStrings().trimResults();
104 return keySplitter.splitToList(path);
107 public static void validateXPath(final StmtContext<?, ?, ?> ctx, final String path) {
109 final XPath xPath = XPathFactory.newInstance().newXPath();
113 } catch (XPathExpressionException e) {
114 throw new IllegalArgumentException(String.format("Argument %s is not valid XPath string at %s", path, ctx
115 .getStatementSourceReference().toString()), e);
119 private static String trimSingleLastSlashFromXPath(final String path) {
120 return path.replaceAll("/$", "");
123 public static boolean isXPathAbsolute(final StmtContext<?, ?, ?> ctx, final String path) {
125 validateXPath(ctx, trimSingleLastSlashFromXPath(path));
127 return path.matches(REGEX_PATH_ABS);
130 public static QName trimPrefix(final QName identifier) {
131 String prefixedLocalName = identifier.getLocalName();
132 String[] namesParts = prefixedLocalName.split(":");
134 if (namesParts.length == 2) {
135 String localName = namesParts[1];
136 return QName.create(identifier.getModule(), localName);
142 public static String getPrefixFromArgument(final String prefixedLocalName) {
143 String[] namesParts = prefixedLocalName.split(":");
144 if (namesParts.length == 2) {
145 return namesParts[0];
150 public static boolean isValidStatementDefinition(final PrefixToModule prefixes, final QNameToStatementDefinition stmtDef,
151 final QName identifier) {
152 if (stmtDef.get(identifier) != null) {
155 String prefixedLocalName = identifier.getLocalName();
156 String[] namesParts = prefixedLocalName.split(":");
158 if (namesParts.length == 2) {
159 String prefix = namesParts[0];
160 String localName = namesParts[1];
161 if (prefixes != null && prefixes.get(prefix) != null
162 && stmtDef.get(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
165 if (stmtDef.get(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
174 public static Iterable<QName> parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
176 String trimmedPath = trimSingleLastSlashFromXPath(path);
178 validateXPath(ctx, trimmedPath);
180 List<String> nodeNames = splitPathToNodeNames(trimmedPath);
181 List<QName> qNames = new ArrayList<>();
183 for (String nodeName : nodeNames) {
185 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
187 } catch (Exception e) {
188 throw new IllegalArgumentException(e);
195 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
196 StringBuilder sb = new StringBuilder();
197 List<TerminalNode> strings = context.STRING();
198 if (strings.isEmpty()) {
199 strings = Arrays.asList(context.IDENTIFIER());
201 for (TerminalNode stringNode : strings) {
202 final String str = stringNode.getText();
203 char firstChar = str.charAt(0);
204 final CharMatcher quoteMatcher;
205 if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
206 quoteMatcher = SINGLE_QUOTE_MATCHER;
207 } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
208 quoteMatcher = DOUBLE_QUOTE_MATCHER;
213 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
215 return sb.toString();
218 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
220 if (value == null || value.equals("")) {
221 return ctx.getPublicDefinition().getStatementName();
225 QNameModule qNameModule = null;
226 String localName = null;
228 String[] namesParts = value.split(":");
229 switch (namesParts.length) {
231 localName = namesParts[0];
232 qNameModule = getRootModuleQName(ctx);
235 prefix = namesParts[0];
236 localName = namesParts[1];
237 qNameModule = getModuleQNameByPrefix(ctx, prefix);
238 // in case of unknown statement argument, we're not going to parse it
239 if (qNameModule == null
240 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
241 .isAssignableFrom(UnknownStatementImpl.class)) {
243 qNameModule = getRootModuleQName(ctx);
245 if (qNameModule == null
246 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
247 ctx = ctx.getOriginalCtx();
248 qNameModule = getModuleQNameByPrefix(ctx, prefix);
253 if (qNameModule == null) {
254 throw new IllegalArgumentException("Error in module '" + ctx.getRoot().rawStatementArgument()
255 + "': can not resolve QNameModule for '" + value + "'.");
258 QNameModule resultQNameModule = qNameModule.getRevision() == null ? QNameModule.create(
259 qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
261 return QName.create(resultQNameModule, localName);
264 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
265 QNameModule qNameModule;
266 ModuleIdentifier impModIdentifier = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
267 qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, impModIdentifier);
269 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
270 String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
271 qNameModule = ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
276 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
282 StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
283 QNameModule qNameModule = null;
285 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
286 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
287 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
288 String belongsToModuleName = firstAttributeOf(rootCtx.substatements(),
289 BelongsToStatement.class);
290 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
293 return qNameModule.getRevision() == null ? QNameModule.create(qNameModule.getNamespace(),
294 SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
298 public static StatementContextBase<?, ?, ?> findNode(final StatementContextBase<?, ?, ?> rootStmtCtx,
299 final Iterable<QName> path) {
301 StatementContextBase<?, ?, ?> parent = rootStmtCtx;
303 Iterator<QName> pathIter = path.iterator();
304 while (pathIter.hasNext()) {
305 QName nextPathQName = pathIter.next();
306 StatementContextBase<?, ?, ?> foundSubstatement = getSubstatementByQName(parent, nextPathQName);
308 if (foundSubstatement == null) {
311 if (!pathIter.hasNext()) {
312 return foundSubstatement;
315 parent = foundSubstatement;
321 public static StatementContextBase<?, ?, ?> getSubstatementByQName(final StatementContextBase<?, ?, ?> parent,
322 final QName nextPathQName) {
324 Collection<StatementContextBase<?, ?, ?>> declaredSubstatement = parent.declaredSubstatements();
325 Collection<StatementContextBase<?, ?, ?>> effectiveSubstatement = parent.effectiveSubstatements();
327 Collection<StatementContextBase<?, ?, ?>> allSubstatements = new LinkedList<>();
328 allSubstatements.addAll(declaredSubstatement);
329 allSubstatements.addAll(effectiveSubstatement);
331 for (StatementContextBase<?, ?, ?> substatement : allSubstatements) {
332 if (nextPathQName.equals(substatement.getStatementArgument())) {
341 public static StatementContextBase<?, ?, ?> findNode(final StatementContextBase<?, ?, ?> rootStmtCtx,
342 final SchemaNodeIdentifier node) {
343 return findNode(rootStmtCtx, node.getPathFromRoot());
346 public static SchemaPath getSchemaPath(final StmtContext<?, ?, ?> ctx) {
352 final Iterator<StmtContext<?, ?, ?>> iteratorFromRoot = ctx.getStmtContextsFromRoot().iterator();
353 // skip root argument
354 if (iteratorFromRoot.hasNext()) {
355 iteratorFromRoot.next();
358 List<QName> qNamesFromRoot = new LinkedList<>();
359 while (iteratorFromRoot.hasNext()) {
360 StmtContext<?, ?, ?> nextStmtCtx = iteratorFromRoot.next();
361 Object nextStmtArgument = nextStmtCtx.getStatementArgument();
362 if (nextStmtArgument instanceof QName) {
363 QName qname = (QName) nextStmtArgument;
364 if (StmtContextUtils.producesDeclared(nextStmtCtx, UsesStatement.class)) {
367 if (StmtContextUtils.producesDeclared(nextStmtCtx.getParentContext(), ChoiceStatement.class)
368 && isSupportedAsShorthandCase(nextStmtCtx)) {
369 qNamesFromRoot.add(qname);
371 qNamesFromRoot.add(qname);
372 } else if (nextStmtArgument instanceof String) {
373 StatementContextBase<?, ?, ?> originalCtx = ctx
375 final QName qName = (originalCtx != null) ? qNameFromArgument(
376 originalCtx, (String) nextStmtArgument)
377 : qNameFromArgument(ctx, (String) nextStmtArgument);
378 qNamesFromRoot.add(qName);
379 } else if ((StmtContextUtils.producesDeclared(nextStmtCtx, AugmentStatement.class)
380 || StmtContextUtils.producesDeclared(nextStmtCtx, RefineStatement.class))
381 && nextStmtArgument instanceof SchemaNodeIdentifier) {
382 addQNamesFromSchemaNodeIdentifierToList(qNamesFromRoot, (SchemaNodeIdentifier) nextStmtArgument);
383 } else if (isUnknownNode(nextStmtCtx)) {
384 qNamesFromRoot.add(nextStmtCtx.getPublicDefinition().getStatementName());
386 return SchemaPath.SAME;
390 final SchemaPath schemaPath = SchemaPath.create(qNamesFromRoot, true);
394 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
395 return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
396 .isAssignableFrom(UnknownStatementImpl.class);
399 private static boolean isSupportedAsShorthandCase(final StmtContext<?, ?, ?> statementCtx) {
401 Collection<?> supportedCaseShorthands = statementCtx.getFromNamespace(ValidationBundlesNamespace.class,
402 ValidationBundleType.SUPPORTED_CASE_SHORTHANDS);
404 return supportedCaseShorthands == null || supportedCaseShorthands.contains(statementCtx.getPublicDefinition());
407 private static void addQNamesFromSchemaNodeIdentifierToList(final List<QName> qNamesFromRoot,
408 final SchemaNodeIdentifier augmentTargetPath) {
409 Iterator<QName> augmentTargetPathIterator = augmentTargetPath.getPathFromRoot().iterator();
410 while (augmentTargetPathIterator.hasNext()) {
411 qNamesFromRoot.add(augmentTargetPathIterator.next());
415 public static Deviation.Deviate parseDeviateFromString(final String deviate) {
417 // Yang constants should be lowercase so we have throw if value does not
419 String deviateUpper = deviate.toUpperCase();
420 if (Objects.equals(deviate, deviateUpper)) {
421 throw new IllegalArgumentException(String.format("String %s is not valid deviate argument", deviate));
424 // but Java enum is uppercase so we cannot use lowercase here
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);
432 public static Status parseStatus(final String value) {
434 Status status = null;
437 status = Status.CURRENT;
440 status = Status.DEPRECATED;
443 status = Status.OBSOLETE;
446 LOG.warn("Invalid 'status' statement: " + value);
452 public static SchemaPath SchemaNodeIdentifierToSchemaPath(final SchemaNodeIdentifier identifier) {
453 return SchemaPath.create(identifier.getPathFromRoot(), identifier.isAbsolute());
456 public static Date getLatestRevision(final RootStatementContext<?, ?, ?> root) {
457 return getLatestRevision(root.declaredSubstatements());
460 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
461 Date revision = null;
462 for (StmtContext<?, ?, ?> subStmt : subStmts) {
463 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
465 if (revision == null && subStmt.getStatementArgument() != null) {
466 revision = (Date) subStmt.getStatementArgument();
467 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
469 revision = (Date) subStmt.getStatementArgument();
476 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
477 return (o instanceof ModuleIdentifier)
478 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP ||
479 ((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);