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.Arrays;
23 import java.util.Collection;
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.stmt.BelongsToStatement;
48 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
51 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
52 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
53 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
54 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
56 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
57 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
58 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
59 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
60 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
61 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
62 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
63 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
64 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
65 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
66 import org.slf4j.Logger;
67 import org.slf4j.LoggerFactory;
69 public final class Utils {
70 private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
71 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
72 private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
73 private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
74 private static final CharMatcher LEFT_PARENTHESIS_MATCHER = CharMatcher.is('(');
75 private static final CharMatcher RIGHT_PARENTHESIS_MATCHER = CharMatcher.is(')');
76 private static final CharMatcher AMPERSAND_MATCHER = CharMatcher.is('&');
77 private static final CharMatcher QUESTION_MARK_MATCHER = CharMatcher.is('?');
78 private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
79 private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
80 private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
81 private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
82 private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
84 .add("AlchemicalSymbols")
85 .add("AlphabeticPresentationForms")
86 .add("AncientGreekMusicalNotation")
87 .add("AncientGreekNumbers")
88 .add("AncientSymbols")
90 .add("ArabicPresentationForms-A")
91 .add("ArabicPresentationForms-B")
92 .add("ArabicSupplement")
98 .add("BamumSupplement")
102 .add("BlockElements")
104 .add("BopomofoExtended")
107 .add("BraillePatterns")
110 .add("ByzantineMusicalSymbols")
114 .add("CJKCompatibility")
115 .add("CJKCompatibilityForms")
116 .add("CJKCompatibilityIdeographs")
117 .add("CJKCompatibilityIdeographsSupplement")
118 .add("CJKRadicalsSupplement")
120 .add("CJKSymbolsandPunctuation")
121 .add("CJKUnifiedIdeographs")
122 .add("CJKUnifiedIdeographsExtensionA")
123 .add("CJKUnifiedIdeographsExtensionB")
124 .add("CJKUnifiedIdeographsExtensionC")
125 .add("CJKUnifiedIdeographsExtensionD")
126 .add("CombiningDiacriticalMarks")
127 .add("CombiningDiacriticalMarksSupplement")
128 .add("CombiningHalfMarks")
129 .add("CombiningDiacriticalMarksforSymbols")
130 .add("CommonIndicNumberForms")
131 .add("ControlPictures")
133 .add("CountingRodNumerals")
135 .add("CuneiformNumbersandPunctuation")
136 .add("CurrencySymbols")
137 .add("CypriotSyllabary")
139 .add("CyrillicExtended-A")
140 .add("CyrillicExtended-B")
141 .add("CyrillicSupplementary")
144 .add("DevanagariExtended")
147 .add("EgyptianHieroglyphs")
149 .add("EnclosedAlphanumericSupplement")
150 .add("EnclosedAlphanumerics")
151 .add("EnclosedCJKLettersandMonths")
152 .add("EnclosedIdeographicSupplement")
154 .add("EthiopicExtended")
155 .add("EthiopicExtended-A")
156 .add("EthiopicSupplement")
157 .add("GeneralPunctuation")
158 .add("GeometricShapes")
160 .add("GeorgianSupplement")
163 .add("GreekandCoptic")
164 .add("GreekExtended")
167 .add("HalfwidthandFullwidthForms")
168 .add("HangulCompatibilityJamo")
170 .add("HangulJamoExtended-A")
171 .add("HangulJamoExtended-B")
172 .add("HangulSyllables")
175 .add("HighPrivateUseSurrogates")
176 .add("HighSurrogates")
178 .add("IdeographicDescriptionCharacters")
179 .add("ImperialAramaic")
180 .add("InscriptionalPahlavi")
181 .add("InscriptionalParthian")
182 .add("IPAExtensions")
185 .add("KanaSupplement")
187 .add("Kangxi Radicals")
190 .add("KatakanaPhoneticExtensions")
196 .add("Latin-1Supplement")
197 .add("LatinExtended-A")
198 .add("LatinExtendedAdditional")
199 .add("LatinExtended-B")
200 .add("LatinExtended-C")
201 .add("LatinExtended-D")
203 .add("LetterlikeSymbols")
205 .add("LinearBIdeograms")
206 .add("LinearBSyllabary")
208 .add("LowSurrogates")
214 .add("MathematicalAlphanumericSymbols")
215 .add("MathematicalOperators")
217 .add("MiscellaneousMathematicalSymbols-A")
218 .add("MiscellaneousMathematicalSymbols-B")
219 .add("MiscellaneousSymbols")
220 .add("MiscellaneousSymbolsandArrows")
221 .add("MiscellaneousSymbolsAndPictographs")
222 .add("MiscellaneousTechnical")
223 .add("ModifierToneLetters")
225 .add("MusicalSymbols")
227 .add("MyanmarExtended-A")
235 .add("OldSouthArabian")
237 .add("OpticalCharacterRecognition")
243 .add("PhoneticExtensions")
244 .add("PhoneticExtensionsSupplement")
246 .add("PrivateUseArea")
248 .add("RumiNumeralSymbols")
254 .add("SmallFormVariants")
255 .add("SpacingModifierLetters")
258 .add("SuperscriptsandSubscripts")
259 .add("SupplementalArrows-A")
260 .add("SupplementalArrows-B")
261 .add("SupplementalMathematicalOperators")
262 .add("SupplementalPunctuation")
263 .add("SupplementaryPrivateUseArea-A")
264 .add("SupplementaryPrivateUseArea-B")
273 .add("TaiXuanJingSymbols")
280 .add("TransportAndMapSymbols")
282 .add("UnifiedCanadianAboriginalSyllabics")
283 .add("UnifiedCanadianAboriginalSyllabicsExtended")
285 .add("VariationSelectors")
286 .add("VariationSelectorsSupplement")
287 .add("VedicExtensions")
288 .add("VerticalForms")
291 .add("YijingHexagramSymbols").build();
293 private static final Map<String, Deviate> KEYWORD_TO_DEVIATE_MAP;
295 Builder<String, Deviate> keywordToDeviateMapBuilder = ImmutableMap.builder();
296 for (Deviate deviate : Deviation.Deviate.values()) {
297 keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
299 KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
302 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
304 protected XPathFactory initialValue() {
305 return XPathFactory.newInstance();
310 throw new UnsupportedOperationException();
314 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
315 * caches to them. Invoke this method if you want to detach those resources.
317 public static void detachFromCurrentThread() {
318 XPATH_FACTORY.remove();
321 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
322 final String value) {
323 List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
325 // to detect if key contains duplicates
326 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
327 // FIXME: report all duplicate keys
328 throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
331 Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
333 for (String keyToken : keyTokens) {
335 SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
336 Utils.qNameFromArgument(ctx, keyToken));
337 keyNodes.add(keyNode);
343 private static String trimSingleLastSlashFromXPath(final String path) {
344 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
347 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
348 final XPath xPath = XPATH_FACTORY.get().newXPath();
349 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
351 final String trimmed = trimSingleLastSlashFromXPath(path);
353 // TODO: we could capture the result and expose its 'evaluate' method
354 xPath.compile(trimmed);
355 } catch (XPathExpressionException e) {
356 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
359 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
362 public static QName trimPrefix(final QName identifier) {
363 String prefixedLocalName = identifier.getLocalName();
364 String[] namesParts = prefixedLocalName.split(":");
366 if (namesParts.length == 2) {
367 String localName = namesParts[1];
368 return QName.create(identifier.getModule(), localName);
376 * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
377 * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
378 * This applies to any declared statement, including unknown statements.
380 * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
381 * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
382 * @param identifier - statement to parse from source
383 * @return valid QName for declared statement to be written
386 public static QName getValidStatementDefinition(final PrefixToModule prefixes, final QNameToStatementDefinition
387 stmtDef, final QName identifier) {
388 if (stmtDef.get(identifier) != null) {
389 return stmtDef.get(identifier).getStatementName();
391 String prefixedLocalName = identifier.getLocalName();
392 String[] namesParts = prefixedLocalName.split(":");
394 if (namesParts.length == 2) {
395 String prefix = namesParts[0];
396 String localName = namesParts[1];
397 if (prefixes != null && prefixes.get(prefix) != null
398 && stmtDef.get(QName.create(prefixes.get(prefix), localName)) != null) {
399 return QName.create(prefixes.get(prefix), localName);
406 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
407 // FIXME: is the path trimming really necessary??
408 final List<QName> qNames = new ArrayList<>();
409 for (String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
411 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
413 } catch (Exception e) {
414 throw new IllegalArgumentException(
415 String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
419 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
422 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
423 StringBuilder sb = new StringBuilder();
424 List<TerminalNode> strings = context.STRING();
425 if (strings.isEmpty()) {
426 strings = Arrays.asList(context.IDENTIFIER());
428 for (TerminalNode stringNode : strings) {
429 final String str = stringNode.getText();
430 char firstChar = str.charAt(0);
431 final CharMatcher quoteMatcher;
432 if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
433 quoteMatcher = SINGLE_QUOTE_MATCHER;
434 } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
435 quoteMatcher = DOUBLE_QUOTE_MATCHER;
440 sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
442 return sb.toString();
445 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
446 if (Strings.isNullOrEmpty(value)) {
447 return ctx.getPublicDefinition().getStatementName();
451 QNameModule qNameModule = null;
452 String localName = null;
454 String[] namesParts = value.split(":");
455 switch (namesParts.length) {
457 localName = namesParts[0];
458 qNameModule = getRootModuleQName(ctx);
461 prefix = namesParts[0];
462 localName = namesParts[1];
463 qNameModule = getModuleQNameByPrefix(ctx, prefix);
464 // in case of unknown statement argument, we're not going to parse it
465 if (qNameModule == null
466 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
467 .isAssignableFrom(UnknownStatementImpl.class)) {
469 qNameModule = getRootModuleQName(ctx);
471 if (qNameModule == null
472 && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
473 ctx = ctx.getOriginalCtx();
474 qNameModule = getModuleQNameByPrefix(ctx, prefix);
479 Preconditions.checkArgument(qNameModule != null,
480 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
481 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
482 final QNameModule resultQNameModule;
483 if (qNameModule.getRevision() == null) {
484 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
487 resultQNameModule = qNameModule;
490 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
493 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
494 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
495 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
497 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
498 String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
499 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
504 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
509 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
510 final QNameModule qNameModule;
512 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
513 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
514 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
515 final String belongsToModuleName = firstAttributeOf(rootCtx.substatements(), BelongsToStatement.class);
516 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
521 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
522 if (qNameModule.getRevision() != null) {
526 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
530 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
531 final SchemaNodeIdentifier node) {
532 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
535 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
536 return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
537 .isAssignableFrom(UnknownStatementImpl.class);
540 public static Deviation.Deviate parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
541 return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
542 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
543 ctx.getStatementSourceReference());
546 public static Status parseStatus(final String value) {
549 return Status.CURRENT;
551 return Status.DEPRECATED;
553 return Status.OBSOLETE;
555 LOG.warn("Invalid 'status' statement: {}", value);
560 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
561 Date revision = null;
562 for (StmtContext<?, ?, ?> subStmt : subStmts) {
563 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
565 if (revision == null && subStmt.getStatementArgument() != null) {
566 revision = (Date) subStmt.getStatementArgument();
567 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
569 revision = (Date) subStmt.getStatementArgument();
576 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
577 return (o instanceof ModuleIdentifier)
578 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
579 .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
583 * Replaces illegal characters of QName by the name of the character (e.g.
584 * '?' is replaced by "QuestionMark" etc.).
588 * @return result String
590 public static String replaceIllegalCharsForQName(String string) {
591 string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
592 string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
593 string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
594 string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
599 public static String fixUnicodeScriptPattern(String rawPattern) {
600 for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
602 Pattern.compile(rawPattern);
604 } catch(PatternSyntaxException ex) {
605 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
606 if (ex.getMessage().contains("Unknown character script name")) {
607 rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
614 LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
618 private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
619 StringBuilder result = new StringBuilder(rawPattern);
620 Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
621 if (matcher.find()) {
622 String capturedGroup = matcher.group(1);
623 if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
624 int idx = rawPattern.indexOf("Is" + capturedGroup);
625 result = result.replace(idx, idx + 2, "In");
628 return result.toString();
631 public static boolean belongsToTheSameModule(QName targetStmtQName, QName sourceStmtQName) {
632 if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
638 public static boolean isPresenceContainer(StatementContextBase<?, ?, ?> targetCtx) {
639 if (!targetCtx.getPublicDefinition().equals(Rfc6020Mapping.CONTAINER)) {
643 final List<StatementContextBase<?, ?, ?>> targetSubStatements = new ImmutableList.Builder<StatementContextBase<?, ?, ?>>()
644 .addAll(targetCtx.declaredSubstatements()).addAll(targetCtx.effectiveSubstatements()).build();
645 for (final StatementContextBase<?, ?, ?> subStatement : targetSubStatements) {
646 if (subStatement.getPublicDefinition().equals(Rfc6020Mapping.PRESENCE)) {