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.ImmutableMap;
17 import com.google.common.collect.ImmutableMap.Builder;
18 import com.google.common.collect.ImmutableSet;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Date;
23 import java.util.HashSet;
24 import java.util.List;
26 import java.util.Optional;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import java.util.regex.PatternSyntaxException;
31 import javax.annotation.Nullable;
32 import javax.xml.xpath.XPath;
33 import javax.xml.xpath.XPathExpressionException;
34 import javax.xml.xpath.XPathFactory;
35 import org.antlr.v4.runtime.tree.TerminalNode;
36 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.common.QNameModule;
39 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
40 import org.opendaylight.yangtools.yang.model.api.DeviateKind;
41 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
42 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
43 import org.opendaylight.yangtools.yang.model.api.Status;
44 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
45 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
46 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
47 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
48 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
49 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
50 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
51 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
52 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
53 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
54 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
56 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
57 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
58 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
59 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
60 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
61 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
62 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
63 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
64 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
65 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
66 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
67 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
68 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
72 public final class Utils {
73 private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
74 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
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 Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
82 private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
83 private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
84 private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
86 .add("AlchemicalSymbols")
87 .add("AlphabeticPresentationForms")
88 .add("AncientGreekMusicalNotation")
89 .add("AncientGreekNumbers")
90 .add("AncientSymbols")
92 .add("ArabicPresentationForms-A")
93 .add("ArabicPresentationForms-B")
94 .add("ArabicSupplement")
100 .add("BamumSupplement")
104 .add("BlockElements")
106 .add("BopomofoExtended")
109 .add("BraillePatterns")
112 .add("ByzantineMusicalSymbols")
116 .add("CJKCompatibility")
117 .add("CJKCompatibilityForms")
118 .add("CJKCompatibilityIdeographs")
119 .add("CJKCompatibilityIdeographsSupplement")
120 .add("CJKRadicalsSupplement")
122 .add("CJKSymbolsandPunctuation")
123 .add("CJKUnifiedIdeographs")
124 .add("CJKUnifiedIdeographsExtensionA")
125 .add("CJKUnifiedIdeographsExtensionB")
126 .add("CJKUnifiedIdeographsExtensionC")
127 .add("CJKUnifiedIdeographsExtensionD")
128 .add("CombiningDiacriticalMarks")
129 .add("CombiningDiacriticalMarksSupplement")
130 .add("CombiningHalfMarks")
131 .add("CombiningDiacriticalMarksforSymbols")
132 .add("CommonIndicNumberForms")
133 .add("ControlPictures")
135 .add("CountingRodNumerals")
137 .add("CuneiformNumbersandPunctuation")
138 .add("CurrencySymbols")
139 .add("CypriotSyllabary")
141 .add("CyrillicExtended-A")
142 .add("CyrillicExtended-B")
143 .add("CyrillicSupplementary")
146 .add("DevanagariExtended")
149 .add("EgyptianHieroglyphs")
151 .add("EnclosedAlphanumericSupplement")
152 .add("EnclosedAlphanumerics")
153 .add("EnclosedCJKLettersandMonths")
154 .add("EnclosedIdeographicSupplement")
156 .add("EthiopicExtended")
157 .add("EthiopicExtended-A")
158 .add("EthiopicSupplement")
159 .add("GeneralPunctuation")
160 .add("GeometricShapes")
162 .add("GeorgianSupplement")
165 .add("GreekandCoptic")
166 .add("GreekExtended")
169 .add("HalfwidthandFullwidthForms")
170 .add("HangulCompatibilityJamo")
172 .add("HangulJamoExtended-A")
173 .add("HangulJamoExtended-B")
174 .add("HangulSyllables")
177 .add("HighPrivateUseSurrogates")
178 .add("HighSurrogates")
180 .add("IdeographicDescriptionCharacters")
181 .add("ImperialAramaic")
182 .add("InscriptionalPahlavi")
183 .add("InscriptionalParthian")
184 .add("IPAExtensions")
187 .add("KanaSupplement")
189 .add("Kangxi Radicals")
192 .add("KatakanaPhoneticExtensions")
198 .add("Latin-1Supplement")
199 .add("LatinExtended-A")
200 .add("LatinExtendedAdditional")
201 .add("LatinExtended-B")
202 .add("LatinExtended-C")
203 .add("LatinExtended-D")
205 .add("LetterlikeSymbols")
207 .add("LinearBIdeograms")
208 .add("LinearBSyllabary")
210 .add("LowSurrogates")
216 .add("MathematicalAlphanumericSymbols")
217 .add("MathematicalOperators")
219 .add("MiscellaneousMathematicalSymbols-A")
220 .add("MiscellaneousMathematicalSymbols-B")
221 .add("MiscellaneousSymbols")
222 .add("MiscellaneousSymbolsandArrows")
223 .add("MiscellaneousSymbolsAndPictographs")
224 .add("MiscellaneousTechnical")
225 .add("ModifierToneLetters")
227 .add("MusicalSymbols")
229 .add("MyanmarExtended-A")
237 .add("OldSouthArabian")
239 .add("OpticalCharacterRecognition")
245 .add("PhoneticExtensions")
246 .add("PhoneticExtensionsSupplement")
248 .add("PrivateUseArea")
250 .add("RumiNumeralSymbols")
256 .add("SmallFormVariants")
257 .add("SpacingModifierLetters")
260 .add("SuperscriptsandSubscripts")
261 .add("SupplementalArrows-A")
262 .add("SupplementalArrows-B")
263 .add("SupplementalMathematicalOperators")
264 .add("SupplementalPunctuation")
265 .add("SupplementaryPrivateUseArea-A")
266 .add("SupplementaryPrivateUseArea-B")
275 .add("TaiXuanJingSymbols")
282 .add("TransportAndMapSymbols")
284 .add("UnifiedCanadianAboriginalSyllabics")
285 .add("UnifiedCanadianAboriginalSyllabicsExtended")
287 .add("VariationSelectors")
288 .add("VariationSelectorsSupplement")
289 .add("VedicExtensions")
290 .add("VerticalForms")
293 .add("YijingHexagramSymbols").build();
295 private static final Map<String, DeviateKind> KEYWORD_TO_DEVIATE_MAP;
297 final Builder<String, DeviateKind> keywordToDeviateMapBuilder = ImmutableMap.builder();
298 for (final DeviateKind deviate : DeviateKind.values()) {
299 keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
301 KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
304 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
306 protected XPathFactory initialValue() {
307 return XPathFactory.newInstance();
312 throw new UnsupportedOperationException();
316 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
317 * caches to them. Invoke this method if you want to detach those resources.
319 public static void detachFromCurrentThread() {
320 XPATH_FACTORY.remove();
323 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
324 final String value) {
325 final List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
327 // to detect if key contains duplicates
328 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
329 // FIXME: report all duplicate keys
330 throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
333 final Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
335 for (final String keyToken : keyTokens) {
337 final SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
338 Utils.qNameFromArgument(ctx, keyToken));
339 keyNodes.add(keyNode);
345 static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
346 final String argumentValue) {
347 final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
348 for (final String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
349 final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
350 SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
351 "Unique statement argument '%s' contains schema node identifier '%s' "
352 + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
353 uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
355 return ImmutableSet.copyOf(uniqueConstraintNodes);
358 private static String trimSingleLastSlashFromXPath(final String path) {
359 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
362 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
363 final XPath xPath = XPATH_FACTORY.get().newXPath();
364 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
366 final String trimmed = trimSingleLastSlashFromXPath(path);
368 // TODO: we could capture the result and expose its 'evaluate' method
369 xPath.compile(trimmed);
370 } catch (final XPathExpressionException e) {
371 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
374 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
377 public static QName trimPrefix(final QName identifier) {
378 final String prefixedLocalName = identifier.getLocalName();
379 final String[] namesParts = prefixedLocalName.split(":");
381 if (namesParts.length == 2) {
382 final String localName = namesParts[1];
383 return QName.create(identifier.getModule(), localName);
389 public static String trimPrefix(final String identifier) {
390 final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
391 if (namesParts.size() == 2) {
392 return namesParts.get(1);
399 * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
400 * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
401 * This applies to any declared statement, including unknown statements.
403 * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
404 * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
405 * @param identifier - statement to parse from source
406 * @return valid QName for declared statement to be written
409 public static QName getValidStatementDefinition(final PrefixToModule prefixes,
410 final QNameToStatementDefinition stmtDef, final QName identifier) {
411 final StatementDefinition def = stmtDef.get(identifier);
413 return def.getStatementName();
415 if (prefixes == null) {
419 final String prefixedLocalName = identifier.getLocalName();
420 final String[] namesParts = prefixedLocalName.split(":");
421 if (namesParts.length != 2) {
425 final String prefix = namesParts[0];
426 final String localName = namesParts[1];
427 final QNameModule qNameModule = prefixes.get(prefix);
428 if (qNameModule == null) {
432 if (prefixes.isPreLinkageMap()) {
433 final StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
435 return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
438 final QName qName = QName.create(qNameModule, localName);
439 return stmtDef.get(qName) != null ? qName : null;
442 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
443 // FIXME: is the path trimming really necessary??
444 final List<QName> qNames = new ArrayList<>();
445 for (final String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
447 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
449 } catch (final RuntimeException e) {
450 throw new SourceException(ctx.getStatementSourceReference(), e,
451 "Failed to parse node '%s' in path '%s'", nodeName, path);
455 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
458 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
459 final StringBuilder sb = new StringBuilder();
460 List<TerminalNode> strings = context.STRING();
461 if (strings.isEmpty()) {
462 strings = Collections.singletonList(context.IDENTIFIER());
464 for (final TerminalNode stringNode : strings) {
465 final String str = stringNode.getText();
466 final char firstChar = str.charAt(0);
467 final char lastChar = str.charAt(str.length() - 1);
468 if (firstChar == '"' && lastChar == '"') {
469 final String innerStr = str.substring(1, str.length() - 1);
471 * Unescape escaped double quotes, tabs, new line and backslash
472 * in the inner string and trim the result.
474 sb.append(innerStr.replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n")
475 .replace("\\t", "\t"));
476 } else if (firstChar == '\'' && lastChar == '\'') {
478 * According to RFC6020 a single quote character cannot occur in
479 * a single-quoted string, even when preceded by a backslash.
481 sb.append(str.substring(1, str.length() - 1));
486 return sb.toString();
489 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
490 if (Strings.isNullOrEmpty(value)) {
491 return ctx.getPublicDefinition().getStatementName();
495 QNameModule qNameModule = null;
496 String localName = null;
498 final String[] namesParts = value.split(":");
499 switch (namesParts.length) {
501 localName = namesParts[0];
502 qNameModule = getRootModuleQName(ctx);
505 prefix = namesParts[0];
506 localName = namesParts[1];
507 qNameModule = getModuleQNameByPrefix(ctx, prefix);
508 // in case of unknown statement argument, we're not going to parse it
509 if (qNameModule == null
510 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
511 .isAssignableFrom(UnknownStatementImpl.class)) {
513 qNameModule = getRootModuleQName(ctx);
515 if (qNameModule == null
516 && ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
517 ctx = ctx.getOriginalCtx();
518 qNameModule = getModuleQNameByPrefix(ctx, prefix);
523 qNameModule = InferenceException.throwIfNull(qNameModule, ctx.getStatementSourceReference(),
524 "Cannot resolve QNameModule for '%s'", value);
526 final QNameModule resultQNameModule;
527 if (qNameModule.getRevision() == null) {
528 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
531 resultQNameModule = qNameModule;
534 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
537 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
538 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
539 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
541 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
542 final String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
543 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
548 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
553 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
554 final QNameModule qNameModule;
556 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
557 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
558 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
559 final String belongsToModuleName = firstAttributeOf(rootCtx.declaredSubstatements(),
560 BelongsToStatement.class);
561 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
566 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
567 if (qNameModule.getRevision() != null) {
571 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
575 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
576 final SchemaNodeIdentifier node) {
577 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
580 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
581 return stmtCtx != null && stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
582 .isAssignableFrom(UnknownStatementImpl.class);
585 public static DeviateKind parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
586 return SourceException.throwIfNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
587 ctx.getStatementSourceReference(), "String '%s' is not valid deviate argument", deviateKeyword);
590 public static Status parseStatus(final String value) {
593 return Status.CURRENT;
595 return Status.DEPRECATED;
597 return Status.OBSOLETE;
599 LOG.warn("Invalid 'status' statement: {}", value);
604 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
605 Date revision = null;
606 for (final StmtContext<?, ?, ?> subStmt : subStmts) {
607 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
609 if (revision == null && subStmt.getStatementArgument() != null) {
610 revision = (Date) subStmt.getStatementArgument();
611 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
613 revision = (Date) subStmt.getStatementArgument();
620 public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
621 return (o instanceof ModuleIdentifier)
622 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
623 .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
627 * Replaces illegal characters of QName by the name of the character (e.g.
628 * '?' is replaced by "QuestionMark" etc.).
632 * @return result String
634 public static String replaceIllegalCharsForQName(String string) {
635 string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
636 string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
637 string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
638 string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
643 public static String fixUnicodeScriptPattern(String rawPattern) {
644 for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
646 Pattern.compile(rawPattern);
648 } catch(final PatternSyntaxException ex) {
649 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
650 if (ex.getMessage().contains("Unknown character script name")) {
651 rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
658 LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
662 private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
663 StringBuilder result = new StringBuilder(rawPattern);
664 final Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
665 if (matcher.find()) {
666 final String capturedGroup = matcher.group(1);
667 if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
668 final int idx = rawPattern.indexOf("Is" + capturedGroup);
669 result = result.replace(idx, idx + 2, "In");
672 return result.toString();
675 public static boolean belongsToTheSameModule(final QName targetStmtQName, final QName sourceStmtQName) {
676 if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
682 public static SourceIdentifier createSourceIdentifier(final RootStatementContext<?, ?, ?> root) {
683 final QNameModule qNameModule = root.getFromNamespace(ModuleCtxToModuleQName.class, root);
684 if (qNameModule != null) {
685 // creates SourceIdentifier for a module
686 return RevisionSourceIdentifier.create((String) root.getStatementArgument(),
687 qNameModule.getFormattedRevision());
690 // creates SourceIdentifier for a submodule
691 final Date revision = Optional.ofNullable(Utils.getLatestRevision(root.declaredSubstatements()))
692 .orElse(SimpleDateFormatUtil.DEFAULT_DATE_REV);
693 final String formattedRevision = SimpleDateFormatUtil.getRevisionFormat().format(revision);
694 return RevisionSourceIdentifier.create((String) root.getStatementArgument(), formattedRevision);