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