0a5bfbc8c02262373e7b10a9cf6ba41e4aea12dd
[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.parser.spi.meta.StmtContextUtils.firstAttributeOf;
11
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;
25 import java.util.Map;
26 import java.util.Optional;
27 import java.util.Set;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30 import java.util.regex.PatternSyntaxException;
31 import javax.annotation.Nullable;
32 import javax.xml.xpath.XPath;
33 import javax.xml.xpath.XPathExpressionException;
34 import javax.xml.xpath.XPathFactory;
35 import org.antlr.v4.runtime.tree.TerminalNode;
36 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
37 import org.opendaylight.yangtools.yang.common.QName;
38 import org.opendaylight.yangtools.yang.common.QNameModule;
39 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
40 import org.opendaylight.yangtools.yang.model.api.DeviateKind;
41 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
42 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
43 import org.opendaylight.yangtools.yang.model.api.Status;
44 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
45 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
46 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
47 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
48 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
49 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
50 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
51 import org.opendaylight.yangtools.yang.model.repo.api.RevisionSourceIdentifier;
52 import org.opendaylight.yangtools.yang.model.repo.api.SourceIdentifier;
53 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
54 import org.opendaylight.yangtools.yang.parser.spi.meta.CopyType;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.InferenceException;
56 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
57 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
58 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
59 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
60 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
61 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
62 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
63 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
64 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
65 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
66 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
67 import org.opendaylight.yangtools.yang.parser.stmt.reactor.RootStatementContext;
68 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
71
72 public final class Utils {
73     private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
74     private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
75     private static final CharMatcher LEFT_PARENTHESIS_MATCHER = CharMatcher.is('(');
76     private static final CharMatcher RIGHT_PARENTHESIS_MATCHER = CharMatcher.is(')');
77     private static final CharMatcher AMPERSAND_MATCHER = CharMatcher.is('&');
78     private static final CharMatcher QUESTION_MARK_MATCHER = CharMatcher.is('?');
79     private static final Splitter SLASH_SPLITTER = Splitter.on('/').omitEmptyStrings().trimResults();
80     private static final Splitter SPACE_SPLITTER = Splitter.on(' ').omitEmptyStrings().trimResults();
81     private static final Splitter COLON_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults();
82     private static final Pattern PATH_ABS = Pattern.compile("/[^/].*");
83     private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
84     private static final Set<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
85             .add("AegeanNumbers")
86             .add("AlchemicalSymbols")
87             .add("AlphabeticPresentationForms")
88             .add("AncientGreekMusicalNotation")
89             .add("AncientGreekNumbers")
90             .add("AncientSymbols")
91             .add("Arabic")
92             .add("ArabicPresentationForms-A")
93             .add("ArabicPresentationForms-B")
94             .add("ArabicSupplement")
95             .add("Armenian")
96             .add("Arrows")
97             .add("Avestan")
98             .add("Balinese")
99             .add("Bamum")
100             .add("BamumSupplement")
101             .add("BasicLatin")
102             .add("Batak")
103             .add("Bengali")
104             .add("BlockElements")
105             .add("Bopomofo")
106             .add("BopomofoExtended")
107             .add("BoxDrawing")
108             .add("Brahmi")
109             .add("BraillePatterns")
110             .add("Buginese")
111             .add("Buhid")
112             .add("ByzantineMusicalSymbols")
113             .add("Carian")
114             .add("Cham")
115             .add("Cherokee")
116             .add("CJKCompatibility")
117             .add("CJKCompatibilityForms")
118             .add("CJKCompatibilityIdeographs")
119             .add("CJKCompatibilityIdeographsSupplement")
120             .add("CJKRadicalsSupplement")
121             .add("CJKStrokes")
122             .add("CJKSymbolsandPunctuation")
123             .add("CJKUnifiedIdeographs")
124             .add("CJKUnifiedIdeographsExtensionA")
125             .add("CJKUnifiedIdeographsExtensionB")
126             .add("CJKUnifiedIdeographsExtensionC")
127             .add("CJKUnifiedIdeographsExtensionD")
128             .add("CombiningDiacriticalMarks")
129             .add("CombiningDiacriticalMarksSupplement")
130             .add("CombiningHalfMarks")
131             .add("CombiningDiacriticalMarksforSymbols")
132             .add("CommonIndicNumberForms")
133             .add("ControlPictures")
134             .add("Coptic")
135             .add("CountingRodNumerals")
136             .add("Cuneiform")
137             .add("CuneiformNumbersandPunctuation")
138             .add("CurrencySymbols")
139             .add("CypriotSyllabary")
140             .add("Cyrillic")
141             .add("CyrillicExtended-A")
142             .add("CyrillicExtended-B")
143             .add("CyrillicSupplementary")
144             .add("Deseret")
145             .add("Devanagari")
146             .add("DevanagariExtended")
147             .add("Dingbats")
148             .add("DominoTiles")
149             .add("EgyptianHieroglyphs")
150             .add("Emoticons")
151             .add("EnclosedAlphanumericSupplement")
152             .add("EnclosedAlphanumerics")
153             .add("EnclosedCJKLettersandMonths")
154             .add("EnclosedIdeographicSupplement")
155             .add("Ethiopic")
156             .add("EthiopicExtended")
157             .add("EthiopicExtended-A")
158             .add("EthiopicSupplement")
159             .add("GeneralPunctuation")
160             .add("GeometricShapes")
161             .add("Georgian")
162             .add("GeorgianSupplement")
163             .add("Glagolitic")
164             .add("Gothic")
165             .add("GreekandCoptic")
166             .add("GreekExtended")
167             .add("Gujarati")
168             .add("Gurmukhi")
169             .add("HalfwidthandFullwidthForms")
170             .add("HangulCompatibilityJamo")
171             .add("HangulJamo")
172             .add("HangulJamoExtended-A")
173             .add("HangulJamoExtended-B")
174             .add("HangulSyllables")
175             .add("Hanunoo")
176             .add("Hebrew")
177             .add("HighPrivateUseSurrogates")
178             .add("HighSurrogates")
179             .add("Hiragana")
180             .add("IdeographicDescriptionCharacters")
181             .add("ImperialAramaic")
182             .add("InscriptionalPahlavi")
183             .add("InscriptionalParthian")
184             .add("IPAExtensions")
185             .add("Javanese")
186             .add("Kaithi")
187             .add("KanaSupplement")
188             .add("Kanbun")
189             .add("Kangxi Radicals")
190             .add("Kannada")
191             .add("Katakana")
192             .add("KatakanaPhoneticExtensions")
193             .add("KayahLi")
194             .add("Kharoshthi")
195             .add("Khmer")
196             .add("KhmerSymbols")
197             .add("Lao")
198             .add("Latin-1Supplement")
199             .add("LatinExtended-A")
200             .add("LatinExtendedAdditional")
201             .add("LatinExtended-B")
202             .add("LatinExtended-C")
203             .add("LatinExtended-D")
204             .add("Lepcha")
205             .add("LetterlikeSymbols")
206             .add("Limbu")
207             .add("LinearBIdeograms")
208             .add("LinearBSyllabary")
209             .add("Lisu")
210             .add("LowSurrogates")
211             .add("Lycian")
212             .add("Lydian")
213             .add("MahjongTiles")
214             .add("Malayalam")
215             .add("Mandaic")
216             .add("MathematicalAlphanumericSymbols")
217             .add("MathematicalOperators")
218             .add("MeeteiMayek")
219             .add("MiscellaneousMathematicalSymbols-A")
220             .add("MiscellaneousMathematicalSymbols-B")
221             .add("MiscellaneousSymbols")
222             .add("MiscellaneousSymbolsandArrows")
223             .add("MiscellaneousSymbolsAndPictographs")
224             .add("MiscellaneousTechnical")
225             .add("ModifierToneLetters")
226             .add("Mongolian")
227             .add("MusicalSymbols")
228             .add("Myanmar")
229             .add("MyanmarExtended-A")
230             .add("NewTaiLue")
231             .add("NKo")
232             .add("NumberForms")
233             .add("Ogham")
234             .add("OlChiki")
235             .add("OldItalic")
236             .add("OldPersian")
237             .add("OldSouthArabian")
238             .add("OldTurkic")
239             .add("OpticalCharacterRecognition")
240             .add("Oriya")
241             .add("Osmanya")
242             .add("Phags-pa")
243             .add("PhaistosDisc")
244             .add("Phoenician")
245             .add("PhoneticExtensions")
246             .add("PhoneticExtensionsSupplement")
247             .add("PlayingCards")
248             .add("PrivateUseArea")
249             .add("Rejang")
250             .add("RumiNumeralSymbols")
251             .add("Runic")
252             .add("Samaritan")
253             .add("Saurashtra")
254             .add("Shavian")
255             .add("Sinhala")
256             .add("SmallFormVariants")
257             .add("SpacingModifierLetters")
258             .add("Specials")
259             .add("Sundanese")
260             .add("SuperscriptsandSubscripts")
261             .add("SupplementalArrows-A")
262             .add("SupplementalArrows-B")
263             .add("SupplementalMathematicalOperators")
264             .add("SupplementalPunctuation")
265             .add("SupplementaryPrivateUseArea-A")
266             .add("SupplementaryPrivateUseArea-B")
267             .add("SylotiNagri")
268             .add("Syriac")
269             .add("Tagalog")
270             .add("Tagbanwa")
271             .add("Tags")
272             .add("TaiLe")
273             .add("TaiTham")
274             .add("TaiViet")
275             .add("TaiXuanJingSymbols")
276             .add("Tamil")
277             .add("Telugu")
278             .add("Thaana")
279             .add("Thai")
280             .add("Tibetan")
281             .add("Tifinagh")
282             .add("TransportAndMapSymbols")
283             .add("Ugaritic")
284             .add("UnifiedCanadianAboriginalSyllabics")
285             .add("UnifiedCanadianAboriginalSyllabicsExtended")
286             .add("Vai")
287             .add("VariationSelectors")
288             .add("VariationSelectorsSupplement")
289             .add("VedicExtensions")
290             .add("VerticalForms")
291             .add("YiRadicals")
292             .add("YiSyllables")
293             .add("YijingHexagramSymbols").build();
294
295     private static final Map<String, DeviateKind> KEYWORD_TO_DEVIATE_MAP;
296     static {
297         final Builder<String, DeviateKind> keywordToDeviateMapBuilder = ImmutableMap.builder();
298         for (final DeviateKind deviate : DeviateKind.values()) {
299             keywordToDeviateMapBuilder.put(deviate.getKeyword(), deviate);
300         }
301         KEYWORD_TO_DEVIATE_MAP = keywordToDeviateMapBuilder.build();
302     }
303
304     private static final ThreadLocal<XPathFactory> XPATH_FACTORY = new ThreadLocal<XPathFactory>() {
305         @Override
306         protected XPathFactory initialValue() {
307             return XPathFactory.newInstance();
308         }
309     };
310
311     private Utils() {
312         throw new UnsupportedOperationException();
313     }
314
315     /**
316      * Cleanup any resources attached to the current thread. Threads interacting with this class can cause thread-local
317      * caches to them. Invoke this method if you want to detach those resources.
318      */
319     public static void detachFromCurrentThread() {
320         XPATH_FACTORY.remove();
321     }
322
323     public static Collection<SchemaNodeIdentifier.Relative> transformKeysStringToKeyNodes(final StmtContext<?, ?, ?> ctx,
324             final String value) {
325         final List<String> keyTokens = SPACE_SPLITTER.splitToList(value);
326
327         // to detect if key contains duplicates
328         if ((new HashSet<>(keyTokens)).size() < keyTokens.size()) {
329             // FIXME: report all duplicate keys
330             throw new SourceException(ctx.getStatementSourceReference(), "Duplicate value in list key: %s", value);
331         }
332
333         final Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
334
335         for (final String keyToken : keyTokens) {
336
337             final SchemaNodeIdentifier.Relative keyNode = (Relative) SchemaNodeIdentifier.Relative.create(false,
338                     Utils.qNameFromArgument(ctx, keyToken));
339             keyNodes.add(keyNode);
340         }
341
342         return keyNodes;
343     }
344
345     static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
346             final String argumentValue) {
347         final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
348         for (final String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
349             final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
350             SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
351                     "Unique statement argument '%s' contains schema node identifier '%s' "
352                             + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
353             uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
354         }
355         return ImmutableSet.copyOf(uniqueConstraintNodes);
356     }
357
358     private static String trimSingleLastSlashFromXPath(final String path) {
359         return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
360     }
361
362     static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
363         final XPath xPath = XPATH_FACTORY.get().newXPath();
364         xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
365
366         final String trimmed = trimSingleLastSlashFromXPath(path);
367         try {
368             // TODO: we could capture the result and expose its 'evaluate' method
369             xPath.compile(trimmed);
370         } catch (final XPathExpressionException e) {
371             LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
372         }
373
374         return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
375     }
376
377     public static QName trimPrefix(final QName identifier) {
378         final String prefixedLocalName = identifier.getLocalName();
379         final String[] namesParts = prefixedLocalName.split(":");
380
381         if (namesParts.length == 2) {
382             final String localName = namesParts[1];
383             return QName.create(identifier.getModule(), localName);
384         }
385
386         return identifier;
387     }
388
389     public static String trimPrefix(final String identifier) {
390         final List<String> namesParts = COLON_SPLITTER.splitToList(identifier);
391         if (namesParts.size() == 2) {
392             return namesParts.get(1);
393         }
394         return identifier;
395     }
396
397     /**
398      *
399      * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
400      * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
401      * This applies to any declared statement, including unknown statements.
402      *
403      * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
404      * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
405      * @param identifier - statement to parse from source
406      * @return valid QName for declared statement to be written
407      *
408      */
409     public static QName getValidStatementDefinition(final PrefixToModule prefixes,
410             final QNameToStatementDefinition stmtDef, final QName identifier) {
411         final StatementDefinition def = stmtDef.get(identifier);
412         if (def != null) {
413             return def.getStatementName();
414         }
415         if (prefixes == null) {
416             return null;
417         }
418
419         final String prefixedLocalName = identifier.getLocalName();
420         final String[] namesParts = prefixedLocalName.split(":");
421         if (namesParts.length != 2) {
422             return null;
423         }
424
425         final String prefix = namesParts[0];
426         final String localName = namesParts[1];
427         final QNameModule qNameModule = prefixes.get(prefix);
428         if (qNameModule == null) {
429             return null;
430         }
431
432         if (prefixes.isPreLinkageMap()) {
433             final StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
434                 localName);
435             return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
436         }
437
438         final QName qName = QName.create(qNameModule, localName);
439         return stmtDef.get(qName) != null ? qName : null;
440     }
441
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))) {
446             try {
447                 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
448                 qNames.add(qName);
449             } catch (final RuntimeException e) {
450                 throw new SourceException(ctx.getStatementSourceReference(), e,
451                         "Failed to parse node '%s' in path '%s'", nodeName, path);
452             }
453         }
454
455         return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
456     }
457
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());
463         }
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);
470                 /*
471                  * Unescape escaped double quotes, tabs, new line and backslash
472                  * in the inner string and trim the result.
473                  */
474                 sb.append(innerStr.replace("\\\"", "\"").replace("\\\\", "\\").replace("\\n", "\n")
475                         .replace("\\t", "\t"));
476             } else if (firstChar == '\'' && lastChar == '\'') {
477                 /*
478                  * According to RFC6020 a single quote character cannot occur in
479                  * a single-quoted string, even when preceded by a backslash.
480                  */
481                 sb.append(str.substring(1, str.length() - 1));
482             } else {
483                 sb.append(str);
484             }
485         }
486         return sb.toString();
487     }
488
489     public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
490         if (Strings.isNullOrEmpty(value)) {
491             return ctx.getPublicDefinition().getStatementName();
492         }
493
494         String prefix;
495         QNameModule qNameModule = null;
496         String localName = null;
497
498         final String[] namesParts = value.split(":");
499         switch (namesParts.length) {
500         case 1:
501             localName = namesParts[0];
502             qNameModule = getRootModuleQName(ctx);
503             break;
504         default:
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)) {
512                 localName = value;
513                 qNameModule = getRootModuleQName(ctx);
514             }
515             if (qNameModule == null
516                     && ctx.getCopyHistory().getLastOperation() == CopyType.ADDED_BY_AUGMENTATION) {
517                 ctx = ctx.getOriginalCtx();
518                 qNameModule = getModuleQNameByPrefix(ctx, prefix);
519             }
520             break;
521         }
522
523         qNameModule = InferenceException.throwIfNull(qNameModule, ctx.getStatementSourceReference(),
524             "Cannot resolve QNameModule for '%s'", value);
525
526         final QNameModule resultQNameModule;
527         if (qNameModule.getRevision() == null) {
528             resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
529                 .intern();
530         } else {
531             resultQNameModule = qNameModule;
532         }
533
534         return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
535     }
536
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);
540
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);
544         }
545         return qNameModule;
546     }
547
548     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
549         if (ctx == null) {
550             return null;
551         }
552
553         final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
554         final QNameModule qNameModule;
555
556         if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
557             qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
558         } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
559             final String belongsToModuleName = firstAttributeOf(rootCtx.declaredSubstatements(),
560                 BelongsToStatement.class);
561             qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
562         } else {
563             qNameModule = null;
564         }
565
566         Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
567         if (qNameModule.getRevision() != null) {
568             return qNameModule;
569         }
570
571         return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
572     }
573
574     @Nullable
575     public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
576             final SchemaNodeIdentifier node) {
577         return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
578     }
579
580     public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
581         return stmtCtx != null && stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
582                 .isAssignableFrom(UnknownStatementImpl.class);
583     }
584
585     public static DeviateKind parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
586         return SourceException.throwIfNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
587             ctx.getStatementSourceReference(), "String '%s' is not valid deviate argument", deviateKeyword);
588     }
589
590     public static Status parseStatus(final String value) {
591         switch (value) {
592         case "current":
593             return Status.CURRENT;
594         case "deprecated":
595             return Status.DEPRECATED;
596         case "obsolete":
597             return Status.OBSOLETE;
598         default:
599             LOG.warn("Invalid 'status' statement: {}", value);
600             return null;
601         }
602     }
603
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
608                     .class)) {
609                 if (revision == null && subStmt.getStatementArgument() != null) {
610                     revision = (Date) subStmt.getStatementArgument();
611                 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
612                         (revision) > 0) {
613                     revision = (Date) subStmt.getStatementArgument();
614                 }
615             }
616         }
617         return revision;
618     }
619
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);
624     }
625
626     /**
627      * Replaces illegal characters of QName by the name of the character (e.g.
628      * '?' is replaced by "QuestionMark" etc.).
629      *
630      * @param string
631      *            input String
632      * @return result String
633      */
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");
639
640         return string;
641     }
642
643     public static String fixUnicodeScriptPattern(String rawPattern) {
644         for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
645             try {
646                 Pattern.compile(rawPattern);
647                 return 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);
652                 } else {
653                     return rawPattern;
654                 }
655             }
656         }
657
658         LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
659         return rawPattern;
660     }
661
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");
670             }
671         }
672         return result.toString();
673     }
674
675     public static boolean belongsToTheSameModule(final QName targetStmtQName, final QName sourceStmtQName) {
676         if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
677             return true;
678         }
679         return false;
680     }
681
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());
688         }
689
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);
695     }
696 }