05993a9c3d31a2ca45dfabf0fd8a4cf0bdef9f81
[yangtools.git] / yang / yang-parser-impl / src / main / java / org / opendaylight / yangtools / yang / parser / stmt / rfc6020 / Utils.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.yangtools.yang.parser.stmt.rfc6020;
9
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;
13
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;
28 import java.util.Map;
29 import java.util.Optional;
30 import java.util.Set;
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;
74
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("/[^/].*");
86     @RegEx
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()
92             .add("AegeanNumbers")
93             .add("AlchemicalSymbols")
94             .add("AlphabeticPresentationForms")
95             .add("AncientGreekMusicalNotation")
96             .add("AncientGreekNumbers")
97             .add("AncientSymbols")
98             .add("Arabic")
99             .add("ArabicPresentationForms-A")
100             .add("ArabicPresentationForms-B")
101             .add("ArabicSupplement")
102             .add("Armenian")
103             .add("Arrows")
104             .add("Avestan")
105             .add("Balinese")
106             .add("Bamum")
107             .add("BamumSupplement")
108             .add("BasicLatin")
109             .add("Batak")
110             .add("Bengali")
111             .add("BlockElements")
112             .add("Bopomofo")
113             .add("BopomofoExtended")
114             .add("BoxDrawing")
115             .add("Brahmi")
116             .add("BraillePatterns")
117             .add("Buginese")
118             .add("Buhid")
119             .add("ByzantineMusicalSymbols")
120             .add("Carian")
121             .add("Cham")
122             .add("Cherokee")
123             .add("CJKCompatibility")
124             .add("CJKCompatibilityForms")
125             .add("CJKCompatibilityIdeographs")
126             .add("CJKCompatibilityIdeographsSupplement")
127             .add("CJKRadicalsSupplement")
128             .add("CJKStrokes")
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")
141             .add("Coptic")
142             .add("CountingRodNumerals")
143             .add("Cuneiform")
144             .add("CuneiformNumbersandPunctuation")
145             .add("CurrencySymbols")
146             .add("CypriotSyllabary")
147             .add("Cyrillic")
148             .add("CyrillicExtended-A")
149             .add("CyrillicExtended-B")
150             .add("CyrillicSupplementary")
151             .add("Deseret")
152             .add("Devanagari")
153             .add("DevanagariExtended")
154             .add("Dingbats")
155             .add("DominoTiles")
156             .add("EgyptianHieroglyphs")
157             .add("Emoticons")
158             .add("EnclosedAlphanumericSupplement")
159             .add("EnclosedAlphanumerics")
160             .add("EnclosedCJKLettersandMonths")
161             .add("EnclosedIdeographicSupplement")
162             .add("Ethiopic")
163             .add("EthiopicExtended")
164             .add("EthiopicExtended-A")
165             .add("EthiopicSupplement")
166             .add("GeneralPunctuation")
167             .add("GeometricShapes")
168             .add("Georgian")
169             .add("GeorgianSupplement")
170             .add("Glagolitic")
171             .add("Gothic")
172             .add("GreekandCoptic")
173             .add("GreekExtended")
174             .add("Gujarati")
175             .add("Gurmukhi")
176             .add("HalfwidthandFullwidthForms")
177             .add("HangulCompatibilityJamo")
178             .add("HangulJamo")
179             .add("HangulJamoExtended-A")
180             .add("HangulJamoExtended-B")
181             .add("HangulSyllables")
182             .add("Hanunoo")
183             .add("Hebrew")
184             .add("HighPrivateUseSurrogates")
185             .add("HighSurrogates")
186             .add("Hiragana")
187             .add("IdeographicDescriptionCharacters")
188             .add("ImperialAramaic")
189             .add("InscriptionalPahlavi")
190             .add("InscriptionalParthian")
191             .add("IPAExtensions")
192             .add("Javanese")
193             .add("Kaithi")
194             .add("KanaSupplement")
195             .add("Kanbun")
196             .add("Kangxi Radicals")
197             .add("Kannada")
198             .add("Katakana")
199             .add("KatakanaPhoneticExtensions")
200             .add("KayahLi")
201             .add("Kharoshthi")
202             .add("Khmer")
203             .add("KhmerSymbols")
204             .add("Lao")
205             .add("Latin-1Supplement")
206             .add("LatinExtended-A")
207             .add("LatinExtendedAdditional")
208             .add("LatinExtended-B")
209             .add("LatinExtended-C")
210             .add("LatinExtended-D")
211             .add("Lepcha")
212             .add("LetterlikeSymbols")
213             .add("Limbu")
214             .add("LinearBIdeograms")
215             .add("LinearBSyllabary")
216             .add("Lisu")
217             .add("LowSurrogates")
218             .add("Lycian")
219             .add("Lydian")
220             .add("MahjongTiles")
221             .add("Malayalam")
222             .add("Mandaic")
223             .add("MathematicalAlphanumericSymbols")
224             .add("MathematicalOperators")
225             .add("MeeteiMayek")
226             .add("MiscellaneousMathematicalSymbols-A")
227             .add("MiscellaneousMathematicalSymbols-B")
228             .add("MiscellaneousSymbols")
229             .add("MiscellaneousSymbolsandArrows")
230             .add("MiscellaneousSymbolsAndPictographs")
231             .add("MiscellaneousTechnical")
232             .add("ModifierToneLetters")
233             .add("Mongolian")
234             .add("MusicalSymbols")
235             .add("Myanmar")
236             .add("MyanmarExtended-A")
237             .add("NewTaiLue")
238             .add("NKo")
239             .add("NumberForms")
240             .add("Ogham")
241             .add("OlChiki")
242             .add("OldItalic")
243             .add("OldPersian")
244             .add("OldSouthArabian")
245             .add("OldTurkic")
246             .add("OpticalCharacterRecognition")
247             .add("Oriya")
248             .add("Osmanya")
249             .add("Phags-pa")
250             .add("PhaistosDisc")
251             .add("Phoenician")
252             .add("PhoneticExtensions")
253             .add("PhoneticExtensionsSupplement")
254             .add("PlayingCards")
255             .add("PrivateUseArea")
256             .add("Rejang")
257             .add("RumiNumeralSymbols")
258             .add("Runic")
259             .add("Samaritan")
260             .add("Saurashtra")
261             .add("Shavian")
262             .add("Sinhala")
263             .add("SmallFormVariants")
264             .add("SpacingModifierLetters")
265             .add("Specials")
266             .add("Sundanese")
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")
274             .add("SylotiNagri")
275             .add("Syriac")
276             .add("Tagalog")
277             .add("Tagbanwa")
278             .add("Tags")
279             .add("TaiLe")
280             .add("TaiTham")
281             .add("TaiViet")
282             .add("TaiXuanJingSymbols")
283             .add("Tamil")
284             .add("Telugu")
285             .add("Thaana")
286             .add("Thai")
287             .add("Tibetan")
288             .add("Tifinagh")
289             .add("TransportAndMapSymbols")
290             .add("Ugaritic")
291             .add("UnifiedCanadianAboriginalSyllabics")
292             .add("UnifiedCanadianAboriginalSyllabicsExtended")
293             .add("Vai")
294             .add("VariationSelectors")
295             .add("VariationSelectorsSupplement")
296             .add("VedicExtensions")
297             .add("VerticalForms")
298             .add("YiRadicals")
299             .add("YiSyllables")
300             .add("YijingHexagramSymbols").build();
301
302     private static final Map<String, DeviateKind> KEYWORD_TO_DEVIATE_MAP;
303     static {
304         final Builder<String, DeviateKind> keywordToDeviateMapBuilder = ImmutableMap.builder();
305         for (final DeviateKind deviate : DeviateKind.values()) {
306             keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
307         }
308         KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
309     }
310
311     private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
312         @Override
313         protected XPathFactory initialValue() {
314             return XPathFactory.newInstance();
315         }
316     };
317
318     private Utils() {
319         throw new UnsupportedOperationException();
320     }
321
322     /**
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.
325      */
326     public static void detachFromCurrentThread() {
327         XPATH_FACTORY.remove();
328     }
329
330     public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
331             final String value) {
332         final List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
333
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);
338         }
339
340         final Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
341
342         for (final String keyToken : keyTokens) {
343
344             final SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
345                     Utils.qNameFromArgument(ctx, keyToken));
346             keyNodes.add(keyNode);
347         }
348
349         return keyNodes;
350     }
351
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);
361         }
362         return ImmutableSet.copyOf(uniqueConstraintNodes);
363     }
364
365     private static String trimSingleLastSlashFromXPath(final String path) {
366         return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
367     }
368
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)));
373
374         final String trimmed = trimSingleLastSlashFromXPath(path);
375         try {
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);
385         }
386
387         return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
388     }
389
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());
399             }
400
401             matcher.appendTail(result);
402             return result.toString();
403         }
404
405         return path;
406     }
407
408     public static QName trimPrefix(final QName identifier) {
409         final String prefixedLocalName = identifier.getLocalName();
410         final String[] namesParts = prefixedLocalName.split(":");
411
412         if (namesParts.length == 2) {
413             final String localName = namesParts[1];
414             return QName.create(identifier.getModule(), localName);
415         }
416
417         return identifier;
418     }
419
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);
424         }
425         return identifier;
426     }
427
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))) {
432             try {
433                 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
434                 qNames.add(qName);
435             } catch (final RuntimeException e) {
436                 throw new SourceException(ctx.getStatementSourceReference(), e,
437                         "Failed to parse node '%s' in path '%s'", nodeName, path);
438             }
439         }
440
441         return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
442     }
443
444     public static String stringFromStringContext(final YangStatementParser.ArgumentContext context,
445             final StatementSourceReference ref) {
446         return stringFromStringContext(context, YangVersion.VERSION_1, ref);
447     }
448
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());
455         }
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);
462                 /*
463                  * Unescape escaped double quotes, tabs, new line and backslash
464                  * in the inner string and trim the result.
465                  */
466                 checkDoubleQuotedString(innerStr, yangVersion, ref);
467                 sb.append(innerStr.replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n")
468                         .replace("\\t", "\t"));
469             } else if (firstChar == '\'' && lastChar == '\'') {
470                 /*
471                  * According to RFC6020 a single quote character cannot occur in
472                  * a single-quoted string, even when preceded by a backslash.
473                  */
474                 sb.append(str.substring(1, str.length() - 1));
475             } else {
476                 checkUnquotedString(str, yangVersion, ref);
477                 sb.append(str);
478             }
479         }
480         return sb.toString();
481     }
482
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)) {
488                 case '"':
489                 case '\'':
490                     throw new SourceException(ref, "Yang 1.1: unquoted string (%s) contains illegal characters", str);
491                 }
492             }
493         }
494     }
495
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)) {
502                     case 'n':
503                     case 't':
504                     case '\\':
505                     case '\"':
506                         i++;
507                         break;
508                     default:
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,
512                                 str.charAt(i + 1));
513                     }
514                 }
515             }
516         }
517     }
518
519     public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
520         if (Strings.isNullOrEmpty(value)) {
521             return ctx.getPublicDefinition().getStatementName();
522         }
523
524         String prefix;
525         QNameModule qNameModule = null;
526         String localName = null;
527
528         final String[] namesParts = value.split(":");
529         switch (namesParts.length) {
530         case 1:
531             localName = namesParts[0];
532             qNameModule = getRootModuleQName(ctx);
533             break;
534         default:
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)) {
542                 localName = value;
543                 qNameModule = getRootModuleQName(ctx);
544             }
545             if (qNameModule == null
546                     && ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
547                 ctx = ctx.getOriginalCtx();
548                 qNameModule = getModuleQNameByPrefix(ctx, prefix);
549             }
550             break;
551         }
552
553         qNameModule = InferenceException.throwIfNull(qNameModule, ctx.getStatementSourceReference(),
554             "Cannot resolve QNameModule for '%s'", value);
555
556         final QNameModule resultQNameModule;
557         if (qNameModule.getRevision() == null) {
558             resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
559                 .intern();
560         } else {
561             resultQNameModule = qNameModule;
562         }
563
564         return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
565     }
566
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);
570
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);
574         }
575         return qNameModule;
576     }
577
578     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
579         if (ctx == null) {
580             return null;
581         }
582
583         final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
584         final QNameModule qNameModule;
585
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);
592         } else {
593             qNameModule = null;
594         }
595
596         Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
597         if (qNameModule.getRevision() != null) {
598             return qNameModule;
599         }
600
601         return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
602     }
603
604     @Nullable
605     public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
606             final SchemaNodeIdentifier node) {
607         return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
608     }
609
610     public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
611         return stmtCtx != null && stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
612                 .isAssignableFrom(UnknownStatementImpl.class);
613     }
614
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);
618     }
619
620     public static Status parseStatus(final String value) {
621         switch (value) {
622         case "current":
623             return Status.CURRENT;
624         case "deprecated":
625             return Status.DEPRECATED;
626         case "obsolete":
627             return Status.OBSOLETE;
628         default:
629             LOG.warn("Invalid 'status' statement: {}", value);
630             return null;
631         }
632     }
633
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
638                     .class)) {
639                 if (revision == null && subStmt.getStatementArgument() != null) {
640                     revision = (Date) subStmt.getStatementArgument();
641                 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
642                         (revision) > 0) {
643                     revision = (Date) subStmt.getStatementArgument();
644                 }
645             }
646         }
647         return revision;
648     }
649
650     /**
651      * Replaces illegal characters of QName by the name of the character (e.g.
652      * '?' is replaced by "QuestionMark" etc.).
653      *
654      * @param string
655      *            input String
656      * @return result String
657      */
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");
663
664         return string;
665     }
666
667     public static String fixUnicodeScriptPattern(String rawPattern) {
668         for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
669             try {
670                 Pattern.compile(rawPattern);
671                 return 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);
676                 } else {
677                     return rawPattern;
678                 }
679             }
680         }
681
682         LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
683         return rawPattern;
684     }
685
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");
694             }
695         }
696         return result.toString();
697     }
698
699     public static boolean belongsToTheSameModule(final QName targetStmtQName, final QName sourceStmtQName) {
700         if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
701             return true;
702         }
703         return false;
704     }
705
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());
712         }
713
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);
719     }
720 }