c797294da592fc9268aa6e1ad0b9867899c9c7e0
[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.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableMap.Builder;
19 import com.google.common.collect.ImmutableSet;
20 import com.google.common.collect.Iterables;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31 import java.util.regex.PatternSyntaxException;
32 import javax.annotation.Nullable;
33 import javax.xml.xpath.XPath;
34 import javax.xml.xpath.XPathExpressionException;
35 import javax.xml.xpath.XPathFactory;
36 import org.antlr.v4.runtime.tree.TerminalNode;
37 import org.opendaylight.yangtools.antlrv4.code.gen.YangStatementParser;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.common.QNameModule;
40 import org.opendaylight.yangtools.yang.common.SimpleDateFormatUtil;
41 import org.opendaylight.yangtools.yang.model.api.Deviation;
42 import org.opendaylight.yangtools.yang.model.api.Deviation.Deviate;
43 import org.opendaylight.yangtools.yang.model.api.ModuleIdentifier;
44 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
45 import org.opendaylight.yangtools.yang.model.api.Rfc6020Mapping;
46 import org.opendaylight.yangtools.yang.model.api.Status;
47 import org.opendaylight.yangtools.yang.model.api.meta.StatementDefinition;
48 import org.opendaylight.yangtools.yang.model.api.stmt.BelongsToStatement;
49 import org.opendaylight.yangtools.yang.model.api.stmt.ModuleStatement;
50 import org.opendaylight.yangtools.yang.model.api.stmt.RevisionStatement;
51 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier;
52 import org.opendaylight.yangtools.yang.model.api.stmt.SchemaNodeIdentifier.Relative;
53 import org.opendaylight.yangtools.yang.model.api.stmt.SubmoduleStatement;
54 import org.opendaylight.yangtools.yang.model.util.RevisionAwareXPathImpl;
55 import org.opendaylight.yangtools.yang.parser.spi.meta.QNameCacheNamespace;
56 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContext;
57 import org.opendaylight.yangtools.yang.parser.spi.meta.StmtContextUtils;
58 import org.opendaylight.yangtools.yang.parser.spi.source.BelongsToPrefixToModuleName;
59 import org.opendaylight.yangtools.yang.parser.spi.source.ImpPrefixToModuleIdentifier;
60 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleCtxToModuleQName;
61 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleIdentifierToModuleQName;
62 import org.opendaylight.yangtools.yang.parser.spi.source.ModuleNameToModuleQName;
63 import org.opendaylight.yangtools.yang.parser.spi.source.PrefixToModule;
64 import org.opendaylight.yangtools.yang.parser.spi.source.QNameToStatementDefinition;
65 import org.opendaylight.yangtools.yang.parser.spi.source.SourceException;
66 import org.opendaylight.yangtools.yang.parser.stmt.reactor.StatementContextBase;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
69
70 public final class Utils {
71     private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
72     private static final Logger LOG = LoggerFactory.getLogger(Utils.class);
73     private static final CharMatcher DOUBLE_QUOTE_MATCHER = CharMatcher.is('"');
74     private static final CharMatcher SINGLE_QUOTE_MATCHER = CharMatcher.is('\'');
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, Deviate> KEYWORD_TO_DEVIATE_MAP;
296     static {
297         Builder<String, Deviate> keywordToDeviateMapBuilder = ImmutableMap.builder();
298         for (Deviate deviate : Deviation.Deviate.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         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         Set<SchemaNodeIdentifier.Relative> keyNodes = new HashSet<>();
334
335         for (String keyToken : keyTokens) {
336
337             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 (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 (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         String prefixedLocalName = identifier.getLocalName();
379         String[] namesParts = prefixedLocalName.split(":");
380
381         if (namesParts.length == 2) {
382             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         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         if (stmtDef.get(identifier) != null) {
412             return stmtDef.get(identifier).getStatementName();
413         } else {
414             String prefixedLocalName = identifier.getLocalName();
415             String[] namesParts = prefixedLocalName.split(":");
416
417             if (namesParts.length == 2) {
418                 String prefix = namesParts[0];
419                 String localName = namesParts[1];
420
421                 if (prefixes == null) {
422                     return null;
423                 }
424
425                 QNameModule qNameModule = prefixes.get(prefix);
426                 if (qNameModule == null) {
427                     return null;
428                 }
429
430                 if (prefixes.isPreLinkageMap()) {
431                     StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
432                             localName);
433                     return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
434                 } else {
435                     QName qName = QName.create(qNameModule, localName);
436                     return stmtDef.get(qName) != null ? qName : null;
437                 }
438             }
439         }
440         return null;
441     }
442
443     static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
444         // FIXME: is the path trimming really necessary??
445         final List<QName> qNames = new ArrayList<>();
446         for (String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
447             try {
448                 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
449                 qNames.add(qName);
450             } catch (Exception e) {
451                 throw new IllegalArgumentException(
452                     String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
453             }
454         }
455
456         return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
457     }
458
459     public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
460         StringBuilder sb = new StringBuilder();
461         List<TerminalNode> strings = context.STRING();
462         if (strings.isEmpty()) {
463             strings = Collections.singletonList(context.IDENTIFIER());
464         }
465         for (TerminalNode stringNode : strings) {
466             final String str = stringNode.getText();
467             char firstChar = str.charAt(0);
468             final CharMatcher quoteMatcher;
469             if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
470                 quoteMatcher = SINGLE_QUOTE_MATCHER;
471             } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
472                 quoteMatcher = DOUBLE_QUOTE_MATCHER;
473             } else {
474                 sb.append(str);
475                 continue;
476             }
477             sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
478         }
479         return sb.toString();
480     }
481
482     public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
483         if (Strings.isNullOrEmpty(value)) {
484             return ctx.getPublicDefinition().getStatementName();
485         }
486
487         String prefix;
488         QNameModule qNameModule = null;
489         String localName = null;
490
491         String[] namesParts = value.split(":");
492         switch (namesParts.length) {
493         case 1:
494             localName = namesParts[0];
495             qNameModule = getRootModuleQName(ctx);
496             break;
497         default:
498             prefix = namesParts[0];
499             localName = namesParts[1];
500             qNameModule = getModuleQNameByPrefix(ctx, prefix);
501             // in case of unknown statement argument, we're not going to parse it
502             if (qNameModule == null
503                     && ctx.getPublicDefinition().getDeclaredRepresentationClass()
504                     .isAssignableFrom(UnknownStatementImpl.class)) {
505                 localName = value;
506                 qNameModule = getRootModuleQName(ctx);
507             }
508             if (qNameModule == null
509                     && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
510                 ctx = ctx.getOriginalCtx();
511                 qNameModule = getModuleQNameByPrefix(ctx, prefix);
512             }
513             break;
514         }
515
516         Preconditions.checkArgument(qNameModule != null,
517                 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
518                 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
519         final QNameModule resultQNameModule;
520         if (qNameModule.getRevision() == null) {
521             resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
522                 .intern();
523         } else {
524             resultQNameModule = qNameModule;
525         }
526
527         return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
528     }
529
530     public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
531         final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
532         final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
533
534         if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
535             String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
536             return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
537         }
538         return qNameModule;
539     }
540
541     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
542         if (ctx == null) {
543             return null;
544         }
545
546         final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
547         final QNameModule qNameModule;
548
549         if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
550             qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
551         } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
552             final String belongsToModuleName = firstAttributeOf(rootCtx.substatements(), BelongsToStatement.class);
553             qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
554         } else {
555             qNameModule = null;
556         }
557
558         Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
559         if (qNameModule.getRevision() != null) {
560             return qNameModule;
561         }
562
563         return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
564     }
565
566     @Nullable
567     public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
568             final SchemaNodeIdentifier node) {
569         return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
570     }
571
572     public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
573         return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
574                 .isAssignableFrom(UnknownStatementImpl.class);
575     }
576
577     public static Deviation.Deviate parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
578         return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
579                 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
580                 ctx.getStatementSourceReference());
581     }
582
583     public static Status parseStatus(final String value) {
584         switch (value) {
585         case "current":
586             return Status.CURRENT;
587         case "deprecated":
588             return Status.DEPRECATED;
589         case "obsolete":
590             return Status.OBSOLETE;
591         default:
592             LOG.warn("Invalid 'status' statement: {}", value);
593             return null;
594         }
595     }
596
597     public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
598         Date revision = null;
599         for (StmtContext<?, ?, ?> subStmt : subStmts) {
600             if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
601                     .class)) {
602                 if (revision == null && subStmt.getStatementArgument() != null) {
603                     revision = (Date) subStmt.getStatementArgument();
604                 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
605                         (revision) > 0) {
606                     revision = (Date) subStmt.getStatementArgument();
607                 }
608             }
609         }
610         return revision;
611     }
612
613     public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
614         return (o instanceof ModuleIdentifier)
615                 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
616                         .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
617     }
618
619     /**
620      * Replaces illegal characters of QName by the name of the character (e.g.
621      * '?' is replaced by "QuestionMark" etc.).
622      *
623      * @param string
624      *            input String
625      * @return result String
626      */
627     public static String replaceIllegalCharsForQName(String string) {
628         string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
629         string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
630         string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
631         string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
632
633         return string;
634     }
635
636     public static String fixUnicodeScriptPattern(String rawPattern) {
637         for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
638             try {
639                 Pattern.compile(rawPattern);
640                 return rawPattern;
641             } catch(PatternSyntaxException ex) {
642                 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
643                 if (ex.getMessage().contains("Unknown character script name")) {
644                     rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
645                 } else {
646                     return rawPattern;
647                 }
648             }
649         }
650
651         LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
652         return rawPattern;
653     }
654
655     private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
656         StringBuilder result = new StringBuilder(rawPattern);
657         Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
658         if (matcher.find()) {
659             String capturedGroup = matcher.group(1);
660             if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
661                 int idx = rawPattern.indexOf("Is" + capturedGroup);
662                 result = result.replace(idx, idx + 2, "In");
663             }
664         }
665         return result.toString();
666     }
667
668     public static boolean belongsToTheSameModule(QName targetStmtQName, QName sourceStmtQName) {
669         if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
670             return true;
671         }
672         return false;
673     }
674
675     public static boolean isPresenceContainer(StatementContextBase<?, ?, ?> targetCtx) {
676         if (!targetCtx.getPublicDefinition().equals(Rfc6020Mapping.CONTAINER)) {
677             return false;
678         }
679
680         final List<StatementContextBase<?, ?, ?>> targetSubStatements = new ImmutableList.Builder<StatementContextBase<?, ?, ?>>()
681                 .addAll(targetCtx.declaredSubstatements()).addAll(targetCtx.effectiveSubstatements()).build();
682         for (final StatementContextBase<?, ?, ?> subStatement : targetSubStatements) {
683             if (subStatement.getPublicDefinition().equals(Rfc6020Mapping.PRESENCE)) {
684                 return true;
685             }
686         }
687
688         return false;
689     }
690 }