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