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.Optional;
14 import com.google.common.base.Preconditions;
15 import com.google.common.base.Splitter;
16 import com.google.common.base.Strings;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMap.Builder;
19 import com.google.common.collect.ImmutableSet;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.Date;
24 import java.util.HashSet;
25 import java.util.List;
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.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.meta.CopyType;
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 if (stmtDef.get(identifier) != null) {
411 return stmtDef.get(identifier).getStatementName();
413 final String prefixedLocalName = identifier.getLocalName();
414 final String[] namesParts = prefixedLocalName.split(":");
416 if (namesParts.length == 2) {
417 final String prefix = namesParts[0];
418 final String localName = namesParts[1];
420 if (prefixes == null) {
424 final QNameModule qNameModule = prefixes.get(prefix);
425 if (qNameModule == null) {
429 if (prefixes.isPreLinkageMap()) {
430 final StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
432 return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
434 final QName qName = QName.create(qNameModule, localName);
435 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 Exception e) {
450 throw new IllegalArgumentException(
451 String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
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 Preconditions.checkArgument(qNameModule != null,
524 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
525 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
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.substatements(), 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());
689 // creates SourceIdentifier for a submodule
690 final Date revision = Optional.fromNullable(Utils.getLatestRevision(root.declaredSubstatements()))
691 .or(SimpleDateFormatUtil.DEFAULT_DATE_REV);
692 final String formattedRevision = SimpleDateFormatUtil.getRevisionFormat().format(revision);
693 return RevisionSourceIdentifier.create((String) root.getStatementArgument(),