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.Preconditions;
14 import com.google.common.base.Splitter;
15 import com.google.common.base.Strings;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMap.Builder;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterables;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashSet;
26 import java.util.List;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import java.util.regex.PatternSyntaxException;
32 import javax.annotation.Nullable;
33 import javax.xml.xpath.XPath;
34 import javax.xml.xpath.XPathExpressionException;
35 import javax.xml.xpath.XPathFactory;
36 import org.antlr.v4.runtime.tree.TerminalNode;
37 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.common.QNameModule;
40 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
41 import org.opendaylight.yangtools.yang.model.api.Deviation;
42 import org.opendaylight.yangtools.yang.model.api.Deviation.Deviate;
43 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
44 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
45 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
46 import org.opendaylight.yangtools.yang.model.api.Status;
47 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
48 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
52 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
53 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
54 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
56 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
57 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
58 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
59 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
60 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
61 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
62 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
63 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
64 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
65 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
66 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
70 public final class Utils {
71 private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
72 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
73 private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
74 private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
75 private static final CharMatcher LEFT_PARENTHESIS_MATCHER = CharMatcher.is('(');
76 private static final CharMatcher RIGHT_PARENTHESIS_MATCHER = CharMatcher.is(')');
77 private static final CharMatcher AMPERSAND_MATCHER = CharMatcher.is('&');
78 private static final CharMatcher QUESTION_MARK_MATCHER = CharMatcher.is('?');
79 private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
80 private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
81 private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
82 private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
83 private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
85 .add("AlchemicalSymbols")
86 .add("AlphabeticPresentationForms")
87 .add("AncientGreekMusicalNotation")
88 .add("AncientGreekNumbers")
89 .add("AncientSymbols")
91 .add("ArabicPresentationForms-A")
92 .add("ArabicPresentationForms-B")
93 .add("ArabicSupplement")
99 .add("BamumSupplement")
103 .add("BlockElements")
105 .add("BopomofoExtended")
108 .add("BraillePatterns")
111 .add("ByzantineMusicalSymbols")
115 .add("CJKCompatibility")
116 .add("CJKCompatibilityForms")
117 .add("CJKCompatibilityIdeographs")
118 .add("CJKCompatibilityIdeographsSupplement")
119 .add("CJKRadicalsSupplement")
121 .add("CJKSymbolsandPunctuation")
122 .add("CJKUnifiedIdeographs")
123 .add("CJKUnifiedIdeographsExtensionA")
124 .add("CJKUnifiedIdeographsExtensionB")
125 .add("CJKUnifiedIdeographsExtensionC")
126 .add("CJKUnifiedIdeographsExtensionD")
127 .add("CombiningDiacriticalMarks")
128 .add("CombiningDiacriticalMarksSupplement")
129 .add("CombiningHalfMarks")
130 .add("CombiningDiacriticalMarksforSymbols")
131 .add("CommonIndicNumberForms")
132 .add("ControlPictures")
134 .add("CountingRodNumerals")
136 .add("CuneiformNumbersandPunctuation")
137 .add("CurrencySymbols")
138 .add("CypriotSyllabary")
140 .add("CyrillicExtended-A")
141 .add("CyrillicExtended-B")
142 .add("CyrillicSupplementary")
145 .add("DevanagariExtended")
148 .add("EgyptianHieroglyphs")
150 .add("EnclosedAlphanumericSupplement")
151 .add("EnclosedAlphanumerics")
152 .add("EnclosedCJKLettersandMonths")
153 .add("EnclosedIdeographicSupplement")
155 .add("EthiopicExtended")
156 .add("EthiopicExtended-A")
157 .add("EthiopicSupplement")
158 .add("GeneralPunctuation")
159 .add("GeometricShapes")
161 .add("GeorgianSupplement")
164 .add("GreekandCoptic")
165 .add("GreekExtended")
168 .add("HalfwidthandFullwidthForms")
169 .add("HangulCompatibilityJamo")
171 .add("HangulJamoExtended-A")
172 .add("HangulJamoExtended-B")
173 .add("HangulSyllables")
176 .add("HighPrivateUseSurrogates")
177 .add("HighSurrogates")
179 .add("IdeographicDescriptionCharacters")
180 .add("ImperialAramaic")
181 .add("InscriptionalPahlavi")
182 .add("InscriptionalParthian")
183 .add("IPAExtensions")
186 .add("KanaSupplement")
188 .add("Kangxi Radicals")
191 .add("KatakanaPhoneticExtensions")
197 .add("Latin-1Supplement")
198 .add("LatinExtended-A")
199 .add("LatinExtendedAdditional")
200 .add("LatinExtended-B")
201 .add("LatinExtended-C")
202 .add("LatinExtended-D")
204 .add("LetterlikeSymbols")
206 .add("LinearBIdeograms")
207 .add("LinearBSyllabary")
209 .add("LowSurrogates")
215 .add("MathematicalAlphanumericSymbols")
216 .add("MathematicalOperators")
218 .add("MiscellaneousMathematicalSymbols-A")
219 .add("MiscellaneousMathematicalSymbols-B")
220 .add("MiscellaneousSymbols")
221 .add("MiscellaneousSymbolsandArrows")
222 .add("MiscellaneousSymbolsAndPictographs")
223 .add("MiscellaneousTechnical")
224 .add("ModifierToneLetters")
226 .add("MusicalSymbols")
228 .add("MyanmarExtended-A")
236 .add("OldSouthArabian")
238 .add("OpticalCharacterRecognition")
244 .add("PhoneticExtensions")
245 .add("PhoneticExtensionsSupplement")
247 .add("PrivateUseArea")
249 .add("RumiNumeralSymbols")
255 .add("SmallFormVariants")
256 .add("SpacingModifierLetters")
259 .add("SuperscriptsandSubscripts")
260 .add("SupplementalArrows-A")
261 .add("SupplementalArrows-B")
262 .add("SupplementalMathematicalOperators")
263 .add("SupplementalPunctuation")
264 .add("SupplementaryPrivateUseArea-A")
265 .add("SupplementaryPrivateUseArea-B")
274 .add("TaiXuanJingSymbols")
281 .add("TransportAndMapSymbols")
283 .add("UnifiedCanadianAboriginalSyllabics")
284 .add("UnifiedCanadianAboriginalSyllabicsExtended")
286 .add("VariationSelectors")
287 .add("VariationSelectorsSupplement")
288 .add("VedicExtensions")
289 .add("VerticalForms")
292 .add("YijingHexagramSymbols").build();
294 private static final Map<String, Deviate> KEYWORD_TO_DEVIATE_MAP;
296 Builder<String, Deviate> keywordToDeviateMapBuilder = ImmutableMap.builder();
297 for (Deviate deviate : Deviation.Deviate.values()) {
298 keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
300 KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
303 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
305 protected XPathFactory initialValue() {
306 return XPathFactory.newInstance();
311 throw new UnsupportedOperationException();
315 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
316 * caches to them. Invoke this method if you want to detach those resources.
318 public static void detachFromCurrentThread() {
319 XPATH_FACTORY.remove();
322 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
323 final String value) {
324 List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
326 // to detect if key contains duplicates
327 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
328 // FIXME: report all duplicate keys
329 throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
332 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
334 for (String keyToken : keyTokens) {
336 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
337 Utils.qNameFromArgument(ctx, keyToken));
338 keyNodes.add(keyNode);
344 private static String trimSingleLastSlashFromXPath(final String path) {
345 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
348 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
349 final XPath xPath = XPATH_FACTORY.get().newXPath();
350 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
352 final String trimmed = trimSingleLastSlashFromXPath(path);
354 // TODO: we could capture the result and expose its 'evaluate' method
355 xPath.compile(trimmed);
356 } catch (XPathExpressionException e) {
357 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
360 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
363 public static QName trimPrefix(final QName identifier) {
364 String prefixedLocalName = identifier.getLocalName();
365 String[] namesParts = prefixedLocalName.split(":");
367 if (namesParts.length == 2) {
368 String localName = namesParts[1];
369 return QName.create(identifier.getModule(), localName);
377 * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
378 * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
379 * This applies to any declared statement, including unknown statements.
381 * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
382 * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
383 * @param identifier - statement to parse from source
384 * @return valid QName for declared statement to be written
387 public static QName getValidStatementDefinition(final PrefixToModule prefixes,
388 final QNameToStatementDefinition stmtDef, final QName identifier) {
389 if (stmtDef.get(identifier) != null) {
390 return stmtDef.get(identifier).getStatementName();
392 String prefixedLocalName = identifier.getLocalName();
393 String[] namesParts = prefixedLocalName.split(":");
395 if (namesParts.length == 2) {
396 String prefix = namesParts[0];
397 String localName = namesParts[1];
399 if (prefixes == null) {
403 QNameModule qNameModule = prefixes.get(prefix);
404 if (qNameModule == null) {
408 if (prefixes.isPreLinkageMap()) {
409 StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
411 return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
413 QName qName = QName.create(qNameModule, localName);
414 return stmtDef.get(qName) != null ? qName : null;
421 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
422 // FIXME: is the path trimming really necessary??
423 final List<QName> qNames = new ArrayList<>();
424 for (String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
426 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
428 } catch (Exception e) {
429 throw new IllegalArgumentException(
430 String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
434 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
437 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
438 StringBuilder sb = new StringBuilder();
439 List<TerminalNode> strings = context.STRING();
440 if (strings.isEmpty()) {
441 strings = Collections.singletonList(context.IDENTIFIER());
443 for (TerminalNode stringNode : strings) {
444 final String str = stringNode.getText();
445 char firstChar = str.charAt(0);
446 final CharMatcher quoteMatcher;
447 if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
448 quoteMatcher = SINGLE_QUOTE_MATCHER;
449 } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
450 quoteMatcher = DOUBLE_QUOTE_MATCHER;
455 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
457 return sb.toString();
460 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
461 if (Strings.isNullOrEmpty(value)) {
462 return ctx.getPublicDefinition().getStatementName();
466 QNameModule qNameModule = null;
467 String localName = null;
469 String[] namesParts = value.split(":");
470 switch (namesParts.length) {
472 localName = namesParts[0];
473 qNameModule = getRootModuleQName(ctx);
476 prefix = namesParts[0];
477 localName = namesParts[1];
478 qNameModule = getModuleQNameByPrefix(ctx, prefix);
479 // in case of unknown statement argument, we're not going to parse it
480 if (qNameModule == null
481 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
482 .isAssignableFrom(UnknownStatementImpl.class)) {
484 qNameModule = getRootModuleQName(ctx);
486 if (qNameModule == null
487 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
488 ctx = ctx.getOriginalCtx();
489 qNameModule = getModuleQNameByPrefix(ctx, prefix);
494 Preconditions.checkArgument(qNameModule != null,
495 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
496 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
497 final QNameModule resultQNameModule;
498 if (qNameModule.getRevision() == null) {
499 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
502 resultQNameModule = qNameModule;
505 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
508 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
509 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
510 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
512 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
513 String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
514 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
519 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
524 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
525 final QNameModule qNameModule;
527 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
528 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
529 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
530 final String belongsToModuleName = firstAttributeOf(rootCtx.substatements(), BelongsToStatement.class);
531 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
536 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
537 if (qNameModule.getRevision() != null) {
541 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
545 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
546 final SchemaNodeIdentifier node) {
547 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
550 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
551 return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
552 .isAssignableFrom(UnknownStatementImpl.class);
555 public static Deviation.Deviate parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
556 return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
557 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
558 ctx.getStatementSourceReference());
561 public static Status parseStatus(final String value) {
564 return Status.CURRENT;
566 return Status.DEPRECATED;
568 return Status.OBSOLETE;
570 LOG.warn("Invalid 'status' statement: {}", value);
575 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
576 Date revision = null;
577 for (StmtContext<?, ?, ?> subStmt : subStmts) {
578 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
580 if (revision == null && subStmt.getStatementArgument() != null) {
581 revision = (Date) subStmt.getStatementArgument();
582 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
584 revision = (Date) subStmt.getStatementArgument();
591 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
592 return (o instanceof ModuleIdentifier)
593 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
594 .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
598 * Replaces illegal characters of QName by the name of the character (e.g.
599 * '?' is replaced by "QuestionMark" etc.).
603 * @return result String
605 public static String replaceIllegalCharsForQName(String string) {
606 string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
607 string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
608 string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
609 string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
614 public static String fixUnicodeScriptPattern(String rawPattern) {
615 for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
617 Pattern.compile(rawPattern);
619 } catch(PatternSyntaxException ex) {
620 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
621 if (ex.getMessage().contains("Unknown character script name")) {
622 rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
629 LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
633 private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
634 StringBuilder result = new StringBuilder(rawPattern);
635 Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
636 if (matcher.find()) {
637 String capturedGroup = matcher.group(1);
638 if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
639 int idx = rawPattern.indexOf("Is" + capturedGroup);
640 result = result.replace(idx, idx + 2, "In");
643 return result.toString();
646 public static boolean belongsToTheSameModule(QName targetStmtQName, QName sourceStmtQName) {
647 if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
653 public static boolean isPresenceContainer(StatementContextBase<?, ?, ?> targetCtx) {
654 if (!targetCtx.getPublicDefinition().equals(Rfc6020Mapping.CONTAINER)) {
658 final List<StatementContextBase<?, ?, ?>> targetSubStatements = new ImmutableList.Builder<StatementContextBase<?, ?, ?>>()
659 .addAll(targetCtx.declaredSubstatements()).addAll(targetCtx.effectiveSubstatements()).build();
660 for (final StatementContextBase<?, ?, ?> subStatement : targetSubStatements) {
661 if (subStatement.getPublicDefinition().equals(Rfc6020Mapping.PRESENCE)) {