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