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.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.RootStatementContext;
67 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
68 import org.slf4j.Logger;
69 import org.slf4j.LoggerFactory;
71 public final class Utils {
72 private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
73 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
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 Splitter COLON_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, DeviateKind> KEYWORD_TO_DEVIATE_MAP;
296 final Builder<String, DeviateKind> keywordToDeviateMapBuilder = ImmutableMap.builder();
297 for (final DeviateKind deviate : DeviateKind.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 final 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 final Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
334 for (final String keyToken : keyTokens) {
336 final SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
337 Utils.qNameFromArgument(ctx, keyToken));
338 keyNodes.add(keyNode);
344 static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
345 final String argumentValue) {
346 final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
347 for (final String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
348 final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
349 SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
350 "Unique statement argument '%s' contains schema node identifier '%s' "
351 + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
352 uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
354 return ImmutableSet.copyOf(uniqueConstraintNodes);
357 private static String trimSingleLastSlashFromXPath(final String path) {
358 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
361 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
362 final XPath xPath = XPATH_FACTORY.get().newXPath();
363 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
365 final String trimmed = trimSingleLastSlashFromXPath(path);
367 // TODO: we could capture the result and expose its 'evaluate' method
368 xPath.compile(trimmed);
369 } catch (final XPathExpressionException e) {
370 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
373 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
376 public static QName trimPrefix(final QName identifier) {
377 final String prefixedLocalName = identifier.getLocalName();
378 final String[] namesParts = prefixedLocalName.split(":");
380 if (namesParts.length == 2) {
381 final String localName = namesParts[1];
382 return QName.create(identifier.getModule(), localName);
388 public static String trimPrefix(final String identifier) {
389 final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
390 if (namesParts.size() == 2) {
391 return namesParts.get(1);
398 * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
399 * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
400 * This applies to any declared statement, including unknown statements.
402 * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
403 * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
404 * @param identifier - statement to parse from source
405 * @return valid QName for declared statement to be written
408 public static QName getValidStatementDefinition(final PrefixToModule prefixes,
409 final QNameToStatementDefinition stmtDef, final QName identifier) {
410 final StatementDefinition def = stmtDef.get(identifier);
412 return def.getStatementName();
414 if (prefixes == null) {
418 final String prefixedLocalName = identifier.getLocalName();
419 final String[] namesParts = prefixedLocalName.split(":");
420 if (namesParts.length != 2) {
424 final String prefix = namesParts[0];
425 final String localName = namesParts[1];
426 final QNameModule qNameModule = prefixes.get(prefix);
427 if (qNameModule == null) {
431 if (prefixes.isPreLinkageMap()) {
432 final StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
434 return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
437 final QName qName = QName.create(qNameModule, localName);
438 return stmtDef.get(qName) != null ? qName : null;
441 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
442 // FIXME: is the path trimming really necessary??
443 final List<QName> qNames = new ArrayList<>();
444 for (final String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
446 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
448 } catch (final Exception e) {
449 throw new IllegalArgumentException(
450 String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
454 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
457 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
458 final StringBuilder sb = new StringBuilder();
459 List<TerminalNode> strings = context.STRING();
460 if (strings.isEmpty()) {
461 strings = Collections.singletonList(context.IDENTIFIER());
463 for (final TerminalNode stringNode : strings) {
464 final String str = stringNode.getText();
465 final char firstChar = str.charAt(0);
466 final char lastChar = str.charAt(str.length() - 1);
467 if (firstChar == '"' && lastChar == '"') {
468 final String innerStr = str.substring(1, str.length() - 1);
470 * Unescape escaped double quotes, tabs, new line and backslash
471 * in the inner string and trim the result.
473 sb.append(innerStr.replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n")
474 .replace("\\t", "\t"));
475 } else if (firstChar == '\'' && lastChar == '\'') {
477 * According to RFC6020 a single quote character cannot occur in
478 * a single-quoted string, even when preceded by a backslash.
480 sb.append(str.substring(1, str.length() - 1));
485 return sb.toString();
488 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
489 if (Strings.isNullOrEmpty(value)) {
490 return ctx.getPublicDefinition().getStatementName();
494 QNameModule qNameModule = null;
495 String localName = null;
497 final String[] namesParts = value.split(":");
498 switch (namesParts.length) {
500 localName = namesParts[0];
501 qNameModule = getRootModuleQName(ctx);
504 prefix = namesParts[0];
505 localName = namesParts[1];
506 qNameModule = getModuleQNameByPrefix(ctx, prefix);
507 // in case of unknown statement argument, we're not going to parse it
508 if (qNameModule == null
509 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
510 .isAssignableFrom(UnknownStatementImpl.class)) {
512 qNameModule = getRootModuleQName(ctx);
514 if (qNameModule == null
515 && ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
516 ctx = ctx.getOriginalCtx();
517 qNameModule = getModuleQNameByPrefix(ctx, prefix);
522 Preconditions.checkArgument(qNameModule != null,
523 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
524 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
525 final QNameModule resultQNameModule;
526 if (qNameModule.getRevision() == null) {
527 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
530 resultQNameModule = qNameModule;
533 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
536 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
537 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
538 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
540 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
541 final String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
542 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
547 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
552 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
553 final QNameModule qNameModule;
555 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
556 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
557 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
558 final String belongsToModuleName = firstAttributeOf(rootCtx.declaredSubstatements(),
559 BelongsToStatement.class);
560 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
565 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
566 if (qNameModule.getRevision() != null) {
570 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
574 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
575 final SchemaNodeIdentifier node) {
576 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
579 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
580 return stmtCtx != null && stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
581 .isAssignableFrom(UnknownStatementImpl.class);
584 public static DeviateKind parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
585 return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
586 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
587 ctx.getStatementSourceReference());
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);