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.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;
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;
65 public final class Utils {
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('\'');
71 private static final char SEPARATOR_NODENAME = '/';
73 private static final String REGEX_PATH_ABS = "/[^/].*";
75 public static final char SEPARATOR = ' ';
80 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
82 Splitter keySplitter = Splitter.on(SEPARATOR).omitEmptyStrings().trimResults();
83 List<String> keyTokens = keySplitter.splitToList(value);
85 // to detect if key contains duplicates
86 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
87 throw new IllegalArgumentException();
90 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
92 for (String keyToken : keyTokens) {
94 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
95 Utils.qNameFromArgument(ctx, keyToken));
96 keyNodes.add(keyNode);
102 public static List<String> splitPathToNodeNames(final String path) {
104 Splitter keySplitter = Splitter.on(SEPARATOR_NODENAME).omitEmptyStrings().trimResults();
105 return keySplitter.splitToList(path);
108 public static void validateXPath(final StmtContext<?, ?, ?> ctx, final String path) {
110 final XPath xPath = XPathFactory.newInstance().newXPath();
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);
120 private static String trimSingleLastSlashFromXPath(final String path) {
121 return path.replaceAll("/$", "");
124 public static boolean isXPathAbsolute(final StmtContext<?, ?, ?> ctx, final String path) {
126 validateXPath(ctx, trimSingleLastSlashFromXPath(path));
128 return path.matches(REGEX_PATH_ABS);
131 public static QName trimPrefix(final QName identifier) {
132 String prefixedLocalName = identifier.getLocalName();
133 String[] namesParts = prefixedLocalName.split(":");
135 if (namesParts.length == 2) {
136 String localName = namesParts[1];
137 return QName.create(identifier.getModule(), localName);
143 public static String getPrefixFromArgument(final String prefixedLocalName) {
144 String[] namesParts = prefixedLocalName.split(":");
145 if (namesParts.length == 2) {
146 return namesParts[0];
151 public static boolean isValidStatementDefinition(final PrefixToModule prefixes, final QNameToStatementDefinition stmtDef,
152 final QName identifier) {
153 if (stmtDef.get(identifier) != null) {
156 String prefixedLocalName = identifier.getLocalName();
157 String[] namesParts = prefixedLocalName.split(":");
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(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
166 if (stmtDef.get(new QName(YangConstants.RFC6020_YIN_NAMESPACE, localName)) != null) {
175 public static Iterable<QName> parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
177 String trimmedPath = trimSingleLastSlashFromXPath(path);
179 validateXPath(ctx, trimmedPath);
181 List<String> nodeNames = splitPathToNodeNames(trimmedPath);
182 List<QName> qNames = new ArrayList<>();
184 for (String nodeName : nodeNames) {
186 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
188 } catch (Exception e) {
189 throw new IllegalArgumentException(e);
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());
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;
214 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
216 return sb.toString();
219 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
221 if (value == null || value.equals("")) {
222 return ctx.getPublicDefinition().getStatementName();
226 QNameModule qNameModule = null;
227 String localName = null;
229 String[] namesParts = value.split(":");
230 switch (namesParts.length) {
232 localName = namesParts[0];
233 qNameModule = getRootModuleQName(ctx);
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)) {
244 qNameModule = getRootModuleQName(ctx);
246 if (qNameModule == null
247 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
248 ctx = ctx.getOriginalCtx();
249 qNameModule = getModuleQNameByPrefix(ctx, prefix);
254 if (qNameModule == null) {
255 throw new IllegalArgumentException("Error in module '" + ctx.getRoot().rawStatementArgument()
256 + "': can not resolve QNameModule for '" + value + "'.");
259 QNameModule resultQNameModule = qNameModule.getRevision() == null ? QNameModule.create(
260 qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
262 return QName.create(resultQNameModule, localName);
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);
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);
277 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
283 StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
284 QNameModule qNameModule = null;
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);
294 return qNameModule.getRevision() == null ? QNameModule.create(qNameModule.getNamespace(),
295 SimpleDateFormatUtil.DEFAULT_DATE_REV) : qNameModule;
299 public static StatementContextBase<?, ?, ?> findNode(final StatementContextBase<?, ?, ?> rootStmtCtx,
300 final Iterable<QName> path) {
302 StatementContextBase<?, ?, ?> parent = rootStmtCtx;
304 Iterator<QName> pathIter = path.iterator();
305 while (pathIter.hasNext()) {
306 QName nextPathQName = pathIter.next();
307 StatementContextBase<?, ?, ?> foundSubstatement = getSubstatementByQName(parent, nextPathQName);
309 if (foundSubstatement == null) {
312 if (!pathIter.hasNext()) {
313 return foundSubstatement;
316 parent = foundSubstatement;
322 public static StatementContextBase<?, ?, ?> getSubstatementByQName(final StatementContextBase<?, ?, ?> parent,
323 final QName nextPathQName) {
325 Collection<StatementContextBase<?, ?, ?>> declaredSubstatement = parent.declaredSubstatements();
326 Collection<StatementContextBase<?, ?, ?>> effectiveSubstatement = parent.effectiveSubstatements();
328 Collection<StatementContextBase<?, ?, ?>> allSubstatements = new LinkedList<>();
329 allSubstatements.addAll(declaredSubstatement);
330 allSubstatements.addAll(effectiveSubstatement);
332 for (StatementContextBase<?, ?, ?> substatement : allSubstatements) {
333 if (nextPathQName.equals(substatement.getStatementArgument())) {
342 public static StatementContextBase<?, ?, ?> findNode(final StatementContextBase<?, ?, ?> rootStmtCtx,
343 final SchemaNodeIdentifier node) {
344 return findNode(rootStmtCtx, node.getPathFromRoot());
347 public static SchemaPath getSchemaPath(final StmtContext<?, ?, ?> ctx) {
353 final Iterator<StmtContext<?, ?, ?>> iteratorFromRoot = ctx.getStmtContextsFromRoot().iterator();
354 // skip root argument
355 if (iteratorFromRoot.hasNext()) {
356 iteratorFromRoot.next();
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)) {
368 if (StmtContextUtils.producesDeclared(nextStmtCtx.getParentContext(), ChoiceStatement.class)
369 && isSupportedAsShorthandCase(nextStmtCtx)) {
370 qNamesFromRoot.add(qname);
372 qNamesFromRoot.add(qname);
373 } else if (nextStmtArgument instanceof String) {
374 StatementContextBase<?, ?, ?> originalCtx = ctx
376 final QName qName = (originalCtx != null) ? qNameFromArgument(
377 originalCtx, (String) nextStmtArgument)
378 : qNameFromArgument(ctx, (String) nextStmtArgument);
379 qNamesFromRoot.add(qName);
380 } else if ((StmtContextUtils.producesDeclared(nextStmtCtx, AugmentStatement.class)
381 || StmtContextUtils.producesDeclared(nextStmtCtx, RefineStatement.class))
382 && nextStmtArgument instanceof SchemaNodeIdentifier) {
383 addQNamesFromSchemaNodeIdentifierToList(qNamesFromRoot, (SchemaNodeIdentifier) nextStmtArgument);
384 } else if (isUnknownNode(nextStmtCtx)) {
385 qNamesFromRoot.add(nextStmtCtx.getPublicDefinition().getStatementName());
387 return SchemaPath.SAME;
391 final SchemaPath schemaPath = SchemaPath.create(qNamesFromRoot, true);
395 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
396 return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
397 .isAssignableFrom(UnknownStatementImpl.class);
400 private static boolean isSupportedAsShorthandCase(final StmtContext<?, ?, ?> statementCtx) {
402 Collection<?> supportedCaseShorthands = statementCtx.getFromNamespace(ValidationBundlesNamespace.class,
403 ValidationBundleType.SUPPORTED_CASE_SHORTHANDS);
405 return supportedCaseShorthands == null || supportedCaseShorthands.contains(statementCtx.getPublicDefinition());
408 private static void addQNamesFromSchemaNodeIdentifierToList(final List<QName> qNamesFromRoot,
409 final SchemaNodeIdentifier augmentTargetPath) {
410 for (QName qname : augmentTargetPath.getPathFromRoot()) {
411 qNamesFromRoot.add(qname);
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 Preconditions.checkArgument(!Objects.equals(deviate, deviateUpper),
421 "String %s is not valid deviate argument", deviate);
423 // but Java enum is uppercase so we cannot use lowercase here
425 return Deviation.Deviate.valueOf(deviateUpper);
426 } catch (IllegalArgumentException e) {
427 throw new IllegalArgumentException(String.format("String %s is not valid deviate argument", deviate), e);
431 public static Status parseStatus(final String value) {
433 Status status = null;
436 status = Status.CURRENT;
439 status = Status.DEPRECATED;
442 status = Status.OBSOLETE;
445 LOG.warn("Invalid 'status' statement: " + value);
451 public static Date getLatestRevision(final RootStatementContext<?, ?, ?> root) {
452 return getLatestRevision(root.declaredSubstatements());
455 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
456 Date revision = null;
457 for (StmtContext<?, ?, ?> subStmt : subStmts) {
458 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
460 if (revision == null && subStmt.getStatementArgument() != null) {
461 revision = (Date) subStmt.getStatementArgument();
462 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
464 revision = (Date) subStmt.getStatementArgument();
471 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
472 return (o instanceof ModuleIdentifier)
473 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP ||
474 ((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);