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