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.common.YangConstants.RFC6020_YANG_NAMESPACE;
11 import static org.opendaylight.yangtools.yang.common.YangConstants.YANG_XPATH_FUNCTIONS_PREFIX;
12 import static org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils.firstAttributeOf;
14 import com.google.common.base.CharMatcher;
15 import com.google.common.base.Preconditions;
16 import com.google.common.base.Splitter;
17 import com.google.common.base.Strings;
18 import com.google.common.collect.ImmutableBiMap;
19 import com.google.common.collect.ImmutableMap;
20 import com.google.common.collect.ImmutableMap.Builder;
21 import com.google.common.collect.ImmutableSet;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.Date;
26 import java.util.HashSet;
27 import java.util.List;
29 import java.util.Optional;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 import java.util.regex.PatternSyntaxException;
34 import javax.annotation.Nullable;
35 import javax.annotation.RegEx;
36 import javax.xml.xpath.XPath;
37 import javax.xml.xpath.XPathExpressionException;
38 import javax.xml.xpath.XPathFactory;
39 import org.antlr.v4.runtime.tree.TerminalNode;
40 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
41 import org.opendaylight.yangtools.yang.common.QName;
42 import org.opendaylight.yangtools.yang.common.QNameModule;
43 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
44 import org.opendaylight.yangtools.yang.common.YangVersion;
45 import org.opendaylight.yangtools.yang.model.api.DeviateKind;
46 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
47 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
48 import org.opendaylight.yangtools.yang.model.api.Status;
49 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
52 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
53 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
54 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
55 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
56 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
57 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
58 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
59 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
60 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
61 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
62 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
63 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
64 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
65 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
66 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
67 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
68 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
69 import org.opendaylight.yangtools.yang.parser.spi.source.StatementSourceReference;
70 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
71 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
75 public final class Utils {
76 private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
77 private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
78 private static final CharMatcher LEFT_PARENTHESIS_MATCHER = CharMatcher.is('(');
79 private static final CharMatcher RIGHT_PARENTHESIS_MATCHER = CharMatcher.is(')');
80 private static final CharMatcher AMPERSAND_MATCHER = CharMatcher.is('&');
81 private static final CharMatcher QUESTION_MARK_MATCHER = CharMatcher.is('?');
82 private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
83 private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
84 private static final Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
85 private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
87 private static final String YANG_XPATH_FUNCTIONS_STRING =
88 "(re-match|deref|derived-from(-or-self)?|enum-value|bit-is-set)(\\()";
89 private static final Pattern YANG_XPATH_FUNCTIONS_PATTERN = Pattern.compile(YANG_XPATH_FUNCTIONS_STRING);
90 private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
91 private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
93 .add("AlchemicalSymbols")
94 .add("AlphabeticPresentationForms")
95 .add("AncientGreekMusicalNotation")
96 .add("AncientGreekNumbers")
97 .add("AncientSymbols")
99 .add("ArabicPresentationForms-A")
100 .add("ArabicPresentationForms-B")
101 .add("ArabicSupplement")
107 .add("BamumSupplement")
111 .add("BlockElements")
113 .add("BopomofoExtended")
116 .add("BraillePatterns")
119 .add("ByzantineMusicalSymbols")
123 .add("CJKCompatibility")
124 .add("CJKCompatibilityForms")
125 .add("CJKCompatibilityIdeographs")
126 .add("CJKCompatibilityIdeographsSupplement")
127 .add("CJKRadicalsSupplement")
129 .add("CJKSymbolsandPunctuation")
130 .add("CJKUnifiedIdeographs")
131 .add("CJKUnifiedIdeographsExtensionA")
132 .add("CJKUnifiedIdeographsExtensionB")
133 .add("CJKUnifiedIdeographsExtensionC")
134 .add("CJKUnifiedIdeographsExtensionD")
135 .add("CombiningDiacriticalMarks")
136 .add("CombiningDiacriticalMarksSupplement")
137 .add("CombiningHalfMarks")
138 .add("CombiningDiacriticalMarksforSymbols")
139 .add("CommonIndicNumberForms")
140 .add("ControlPictures")
142 .add("CountingRodNumerals")
144 .add("CuneiformNumbersandPunctuation")
145 .add("CurrencySymbols")
146 .add("CypriotSyllabary")
148 .add("CyrillicExtended-A")
149 .add("CyrillicExtended-B")
150 .add("CyrillicSupplementary")
153 .add("DevanagariExtended")
156 .add("EgyptianHieroglyphs")
158 .add("EnclosedAlphanumericSupplement")
159 .add("EnclosedAlphanumerics")
160 .add("EnclosedCJKLettersandMonths")
161 .add("EnclosedIdeographicSupplement")
163 .add("EthiopicExtended")
164 .add("EthiopicExtended-A")
165 .add("EthiopicSupplement")
166 .add("GeneralPunctuation")
167 .add("GeometricShapes")
169 .add("GeorgianSupplement")
172 .add("GreekandCoptic")
173 .add("GreekExtended")
176 .add("HalfwidthandFullwidthForms")
177 .add("HangulCompatibilityJamo")
179 .add("HangulJamoExtended-A")
180 .add("HangulJamoExtended-B")
181 .add("HangulSyllables")
184 .add("HighPrivateUseSurrogates")
185 .add("HighSurrogates")
187 .add("IdeographicDescriptionCharacters")
188 .add("ImperialAramaic")
189 .add("InscriptionalPahlavi")
190 .add("InscriptionalParthian")
191 .add("IPAExtensions")
194 .add("KanaSupplement")
196 .add("Kangxi Radicals")
199 .add("KatakanaPhoneticExtensions")
205 .add("Latin-1Supplement")
206 .add("LatinExtended-A")
207 .add("LatinExtendedAdditional")
208 .add("LatinExtended-B")
209 .add("LatinExtended-C")
210 .add("LatinExtended-D")
212 .add("LetterlikeSymbols")
214 .add("LinearBIdeograms")
215 .add("LinearBSyllabary")
217 .add("LowSurrogates")
223 .add("MathematicalAlphanumericSymbols")
224 .add("MathematicalOperators")
226 .add("MiscellaneousMathematicalSymbols-A")
227 .add("MiscellaneousMathematicalSymbols-B")
228 .add("MiscellaneousSymbols")
229 .add("MiscellaneousSymbolsandArrows")
230 .add("MiscellaneousSymbolsAndPictographs")
231 .add("MiscellaneousTechnical")
232 .add("ModifierToneLetters")
234 .add("MusicalSymbols")
236 .add("MyanmarExtended-A")
244 .add("OldSouthArabian")
246 .add("OpticalCharacterRecognition")
252 .add("PhoneticExtensions")
253 .add("PhoneticExtensionsSupplement")
255 .add("PrivateUseArea")
257 .add("RumiNumeralSymbols")
263 .add("SmallFormVariants")
264 .add("SpacingModifierLetters")
267 .add("SuperscriptsandSubscripts")
268 .add("SupplementalArrows-A")
269 .add("SupplementalArrows-B")
270 .add("SupplementalMathematicalOperators")
271 .add("SupplementalPunctuation")
272 .add("SupplementaryPrivateUseArea-A")
273 .add("SupplementaryPrivateUseArea-B")
282 .add("TaiXuanJingSymbols")
289 .add("TransportAndMapSymbols")
291 .add("UnifiedCanadianAboriginalSyllabics")
292 .add("UnifiedCanadianAboriginalSyllabicsExtended")
294 .add("VariationSelectors")
295 .add("VariationSelectorsSupplement")
296 .add("VedicExtensions")
297 .add("VerticalForms")
300 .add("YijingHexagramSymbols").build();
302 private static final Map<String, DeviateKind> KEYWORD_TO_DEVIATE_MAP;
304 final Builder<String, DeviateKind> keywordToDeviateMapBuilder = ImmutableMap.builder();
305 for (final DeviateKind deviate : DeviateKind.values()) {
306 keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
308 KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
311 private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
313 protected XPathFactory initialValue() {
314 return XPathFactory.newInstance();
319 throw new UnsupportedOperationException();
323 * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
324 * caches to them. Invoke this method if you want to detach those resources.
326 public static void detachFromCurrentThread() {
327 XPATH_FACTORY.remove();
330 public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
331 final String value) {
332 final List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
334 // to detect if key contains duplicates
335 if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
336 // FIXME: report all duplicate keys
337 throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
340 final Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
342 for (final String keyToken : keyTokens) {
344 final SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
345 Utils.qNameFromArgument(ctx, keyToken));
346 keyNodes.add(keyNode);
352 static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
353 final String argumentValue) {
354 final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
355 for (final String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
356 final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
357 SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
358 "Unique statement argument '%s' contains schema node identifier '%s' "
359 + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
360 uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
362 return ImmutableSet.copyOf(uniqueConstraintNodes);
365 private static String trimSingleLastSlashFromXPath(final String path) {
366 return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
369 static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
370 final XPath xPath = XPATH_FACTORY.get().newXPath();
371 xPath.setNamespaceContext(StmtNamespaceContext.create(ctx,
372 ImmutableBiMap.of(RFC6020_YANG_NAMESPACE.toString(), YANG_XPATH_FUNCTIONS_PREFIX)));
374 final String trimmed = trimSingleLastSlashFromXPath(path);
376 // XPath extension functions have to be prefixed
377 // yang-specific XPath functions are in fact extended functions, therefore we have to add
378 // "yang" prefix to them so that they can be properly validated with the XPath.compile() method
379 // the "yang" prefix is bound to RFC6020 YANG namespace
380 final String prefixedXPath = addPrefixToYangXPathFunctions(trimmed, ctx);
381 // TODO: we could capture the result and expose its 'evaluate' method
382 xPath.compile(prefixedXPath);
383 } catch (final XPathExpressionException e) {
384 LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
387 return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
390 private static String addPrefixToYangXPathFunctions(final String path, final StmtContext<?, ?, ?> ctx) {
391 if (ctx.getRootVersion() == YangVersion.VERSION_1_1) {
392 // FIXME once Java 9 is available, change this to StringBuilder as Matcher.appendReplacement() and
393 // Matcher.appendTail() will accept StringBuilder parameter in Java 9
394 final StringBuffer result = new StringBuffer();
395 final String prefix = YANG_XPATH_FUNCTIONS_PREFIX + ":";
396 final Matcher matcher = YANG_XPATH_FUNCTIONS_PATTERN.matcher(path);
397 while (matcher.find()) {
398 matcher.appendReplacement(result, prefix + matcher.group());
401 matcher.appendTail(result);
402 return result.toString();
408 public static QName trimPrefix(final QName identifier) {
409 final String prefixedLocalName = identifier.getLocalName();
410 final String[] namesParts = prefixedLocalName.split(":");
412 if (namesParts.length == 2) {
413 final String localName = namesParts[1];
414 return QName.create(identifier.getModule(), localName);
420 public static String trimPrefix(final String identifier) {
421 final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
422 if (namesParts.size() == 2) {
423 return namesParts.get(1);
428 static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
429 // FIXME: is the path trimming really necessary??
430 final List<QName> qNames = new ArrayList<>();
431 for (final String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
433 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
435 } catch (final RuntimeException e) {
436 throw new SourceException(ctx.getStatementSourceReference(), e,
437 "Failed to parse node '%s' in path '%s'", nodeName, path);
441 return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
444 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context,
445 final StatementSourceReference ref) {
446 return stringFromStringContext(context, YangVersion.VERSION_1, ref);
449 public static String stringFromStringContext(final YangStatementParser.ArgumentContext context,
450 final YangVersion yangVersion, final StatementSourceReference ref) {
451 final StringBuilder sb = new StringBuilder();
452 List<TerminalNode> strings = context.STRING();
453 if (strings.isEmpty()) {
454 strings = Collections.singletonList(context.IDENTIFIER());
456 for (final TerminalNode stringNode : strings) {
457 final String str = stringNode.getText();
458 final char firstChar = str.charAt(0);
459 final char lastChar = str.charAt(str.length() - 1);
460 if (firstChar == '"' && lastChar == '"') {
461 final String innerStr = str.substring(1, str.length() - 1);
463 * Unescape escaped double quotes, tabs, new line and backslash
464 * in the inner string and trim the result.
466 checkDoubleQuotedString(innerStr, yangVersion, ref);
467 sb.append(innerStr.replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n")
468 .replace("\\t", "\t"));
469 } else if (firstChar == '\'' && lastChar == '\'') {
471 * According to RFC6020 a single quote character cannot occur in
472 * a single-quoted string, even when preceded by a backslash.
474 sb.append(str.substring(1, str.length() - 1));
476 checkUnquotedString(str, yangVersion, ref);
480 return sb.toString();
483 private static void checkUnquotedString(final String str, final YangVersion yangVersion,
484 final StatementSourceReference ref) {
485 if (yangVersion == YangVersion.VERSION_1_1) {
486 for (int i = 0; i < str.length(); i++) {
487 switch (str.charAt(i)) {
490 throw new SourceException(ref, "Yang 1.1: unquoted string (%s) contains illegal characters", str);
496 private static void checkDoubleQuotedString(final String str, final YangVersion yangVersion,
497 final StatementSourceReference ref) {
498 if (yangVersion == YangVersion.VERSION_1_1) {
499 for (int i = 0; i < str.length() - 1; i++) {
500 if (str.charAt(i) == '\\') {
501 switch (str.charAt(i + 1)) {
509 throw new SourceException(ref,
510 "Yang 1.1: illegal double quoted string (%s). In double quoted string the backslash must be followed "
511 + "by one of the following character [n,t,\",\\], but was '%s'.", str,
519 public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
520 if (Strings.isNullOrEmpty(value)) {
521 return ctx.getPublicDefinition().getStatementName();
525 QNameModule qNameModule = null;
526 String localName = null;
528 final String[] namesParts = value.split(":");
529 switch (namesParts.length) {
531 localName = namesParts[0];
532 qNameModule = getRootModuleQName(ctx);
535 prefix = namesParts[0];
536 localName = namesParts[1];
537 qNameModule = getModuleQNameByPrefix(ctx, prefix);
538 // in case of unknown statement argument, we're not going to parse it
539 if (qNameModule == null
540 && ctx.getPublicDefinition().getDeclaredRepresentationClass()
541 .isAssignableFrom(UnknownStatementImpl.class)) {
543 qNameModule = getRootModuleQName(ctx);
545 if (qNameModule == null
546 && ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
547 ctx = ctx.getOriginalCtx();
548 qNameModule = getModuleQNameByPrefix(ctx, prefix);
553 qNameModule = InferenceException.throwIfNull(qNameModule, ctx.getStatementSourceReference(),
554 "Cannot resolve QNameModule for '%s'", value);
556 final QNameModule resultQNameModule;
557 if (qNameModule.getRevision() == null) {
558 resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
561 resultQNameModule = qNameModule;
564 return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
567 public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
568 final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
569 final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
571 if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
572 final String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
573 return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
578 public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
583 final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
584 final QNameModule qNameModule;
586 if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
587 qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
588 } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
589 final String belongsToModuleName = firstAttributeOf(rootCtx.declaredSubstatements(),
590 BelongsToStatement.class);
591 qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
596 Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
597 if (qNameModule.getRevision() != null) {
601 return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
605 public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
606 final SchemaNodeIdentifier node) {
607 return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
610 public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
611 return stmtCtx != null && stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
612 .isAssignableFrom(UnknownStatementImpl.class);
615 public static DeviateKind parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
616 return SourceException.throwIfNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
617 ctx.getStatementSourceReference(), "String '%s' is not valid deviate argument", deviateKeyword);
620 public static Status parseStatus(final String value) {
623 return Status.CURRENT;
625 return Status.DEPRECATED;
627 return Status.OBSOLETE;
629 LOG.warn("Invalid 'status' statement: {}", value);
634 public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
635 Date revision = null;
636 for (final StmtContext<?, ?, ?> subStmt : subStmts) {
637 if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
639 if (revision == null && subStmt.getStatementArgument() != null) {
640 revision = (Date) subStmt.getStatementArgument();
641 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
643 revision = (Date) subStmt.getStatementArgument();
651 * Replaces illegal characters of QName by the name of the character (e.g.
652 * '?' is replaced by "QuestionMark" etc.).
656 * @return result String
658 public static String replaceIllegalCharsForQName(String string) {
659 string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
660 string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
661 string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
662 string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
667 public static String fixUnicodeScriptPattern(String rawPattern) {
668 for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
670 Pattern.compile(rawPattern);
672 } catch(final PatternSyntaxException ex) {
673 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
674 if (ex.getMessage().contains("Unknown character script name")) {
675 rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
682 LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
686 private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
687 StringBuilder result = new StringBuilder(rawPattern);
688 final Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
689 if (matcher.find()) {
690 final String capturedGroup = matcher.group(1);
691 if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
692 final int idx = rawPattern.indexOf("Is" + capturedGroup);
693 result = result.replace(idx, idx + 2, "In");
696 return result.toString();
699 public static boolean belongsToTheSameModule(final QName targetStmtQName, final QName sourceStmtQName) {
700 if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
706 public static SourceIdentifier createSourceIdentifier(final RootStatementContext<?, ?, ?> root) {
707 final QNameModule qNameModule = root.getFromNamespace(ModuleCtxToModuleQName.class, root);
708 if (qNameModule != null) {
709 // creates SourceIdentifier for a module
710 return RevisionSourceIdentifier.create((String) root.getStatementArgument(),
711 qNameModule.getFormattedRevision());
714 // creates SourceIdentifier for a submodule
715 final Date revision = Optional.ofNullable(Utils.getLatestRevision(root.declaredSubstatements()))
716 .orElse(SimpleDateFormatUtil.DEFAULT_DATE_REV);
717 final String formattedRevision = SimpleDateFormatUtil.getRevisionFormat().format(revision);
718 return RevisionSourceIdentifier.create((String) root.getStatementArgument(), formattedRevision);