BUG-865: integrate SchemaAwareNormalizedNodeStreamWriter
[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     static Collection<SchemaNodeIdentifier.Relative> parseUniqueConstraintArgument(final StmtContext<?, ?, ?> ctx,
345             final String argumentValue) {
346         final Set<SchemaNodeIdentifier.Relative> uniqueConstraintNodes = new HashSet<>();
347         for (String uniqueArgToken : SPACE_SPLITTER.split(argumentValue)) {
348             final SchemaNodeIdentifier nodeIdentifier = Utils.nodeIdentifierFromPath(ctx, uniqueArgToken);
349             SourceException.throwIf(nodeIdentifier.isAbsolute(), ctx.getStatementSourceReference(),
350                     "Unique statement argument '%s' contains schema node identifier '%s' "
351                             + "which is not in the descendant node identifier form.", argumentValue, uniqueArgToken);
352             uniqueConstraintNodes.add((SchemaNodeIdentifier.Relative) nodeIdentifier);
353         }
354         return ImmutableSet.copyOf(uniqueConstraintNodes);
355     }
356
357     private static String trimSingleLastSlashFromXPath(final String path) {
358         return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
359     }
360
361     static RevisionAwareXPath parseXPath(final StmtContext<?, ?, ?> ctx, final String path) {
362         final XPath xPath = XPATH_FACTORY.get().newXPath();
363         xPath.setNamespaceContext(StmtNamespaceContext.create(ctx));
364
365         final String trimmed = trimSingleLastSlashFromXPath(path);
366         try {
367             // TODO: we could capture the result and expose its 'evaluate' method
368             xPath.compile(trimmed);
369         } catch (XPathExpressionException e) {
370             LOG.warn("Argument \"{}\" is not valid XPath string at \"{}\"", path, ctx.getStatementSourceReference(), e);
371         }
372
373         return new RevisionAwareXPathImpl(path, PATH_ABS.matcher(path).matches());
374     }
375
376     public static QName trimPrefix(final QName identifier) {
377         String prefixedLocalName = identifier.getLocalName();
378         String[] namesParts = prefixedLocalName.split(":");
379
380         if (namesParts.length == 2) {
381             String localName = namesParts[1];
382             return QName.create(identifier.getModule(), localName);
383         }
384
385         return identifier;
386     }
387
388     /**
389      *
390      * Based on identifier read from source and collections of relevant prefixes and statement definitions mappings
391      * provided for actual phase, method resolves and returns valid QName for declared statement to be written.
392      * This applies to any declared statement, including unknown statements.
393      *
394      * @param prefixes - collection of all relevant prefix mappings supplied for actual parsing phase
395      * @param stmtDef - collection of all relevant statement definition mappings provided for actual parsing phase
396      * @param identifier - statement to parse from source
397      * @return valid QName for declared statement to be written
398      *
399      */
400     public static QName getValidStatementDefinition(final PrefixToModule prefixes,
401             final QNameToStatementDefinition stmtDef, final QName identifier) {
402         if (stmtDef.get(identifier) != null) {
403             return stmtDef.get(identifier).getStatementName();
404         } else {
405             String prefixedLocalName = identifier.getLocalName();
406             String[] namesParts = prefixedLocalName.split(":");
407
408             if (namesParts.length == 2) {
409                 String prefix = namesParts[0];
410                 String localName = namesParts[1];
411
412                 if (prefixes == null) {
413                     return null;
414                 }
415
416                 QNameModule qNameModule = prefixes.get(prefix);
417                 if (qNameModule == null) {
418                     return null;
419                 }
420
421                 if (prefixes.isPreLinkageMap()) {
422                     StatementDefinition foundStmtDef = stmtDef.getByNamespaceAndLocalName(qNameModule.getNamespace(),
423                             localName);
424                     return foundStmtDef != null ? foundStmtDef.getStatementName() : null;
425                 } else {
426                     QName qName = QName.create(qNameModule, localName);
427                     return stmtDef.get(qName) != null ? qName : null;
428                 }
429             }
430         }
431         return null;
432     }
433
434     static SchemaNodeIdentifier nodeIdentifierFromPath(final StmtContext<?, ?, ?> ctx, final String path) {
435         // FIXME: is the path trimming really necessary??
436         final List<QName> qNames = new ArrayList<>();
437         for (String nodeName : SLASH_SPLITTER.split(trimSingleLastSlashFromXPath(path))) {
438             try {
439                 final QName qName = Utils.qNameFromArgument(ctx, nodeName);
440                 qNames.add(qName);
441             } catch (Exception e) {
442                 throw new IllegalArgumentException(
443                     String.format("Failed to parse node '%s' in path '%s'", nodeName, path), e);
444             }
445         }
446
447         return SchemaNodeIdentifier.create(qNames, PATH_ABS.matcher(path).matches());
448     }
449
450     public static String stringFromStringContext(final YangStatementParser.ArgumentContext context) {
451         StringBuilder sb = new StringBuilder();
452         List<TerminalNode> strings = context.STRING();
453         if (strings.isEmpty()) {
454             strings = Collections.singletonList(context.IDENTIFIER());
455         }
456         for (TerminalNode stringNode : strings) {
457             final String str = stringNode.getText();
458             char firstChar = str.charAt(0);
459             final CharMatcher quoteMatcher;
460             if (SINGLE_QUOTE_MATCHER.matches(firstChar)) {
461                 quoteMatcher = SINGLE_QUOTE_MATCHER;
462             } else if (DOUBLE_QUOTE_MATCHER.matches(firstChar)) {
463                 quoteMatcher = DOUBLE_QUOTE_MATCHER;
464             } else {
465                 sb.append(str);
466                 continue;
467             }
468             sb.append(quoteMatcher.removeFrom(str.substring(1, str.length() - 1)));
469         }
470         return sb.toString();
471     }
472
473     public static QName qNameFromArgument(StmtContext<?, ?, ?> ctx, final String value) {
474         if (Strings.isNullOrEmpty(value)) {
475             return ctx.getPublicDefinition().getStatementName();
476         }
477
478         String prefix;
479         QNameModule qNameModule = null;
480         String localName = null;
481
482         String[] namesParts = value.split(":");
483         switch (namesParts.length) {
484         case 1:
485             localName = namesParts[0];
486             qNameModule = getRootModuleQName(ctx);
487             break;
488         default:
489             prefix = namesParts[0];
490             localName = namesParts[1];
491             qNameModule = getModuleQNameByPrefix(ctx, prefix);
492             // in case of unknown statement argument, we're not going to parse it
493             if (qNameModule == null
494                     && ctx.getPublicDefinition().getDeclaredRepresentationClass()
495                     .isAssignableFrom(UnknownStatementImpl.class)) {
496                 localName = value;
497                 qNameModule = getRootModuleQName(ctx);
498             }
499             if (qNameModule == null
500                     && Iterables.getLast(ctx.getCopyHistory()) == StmtContext.TypeOfCopy.ADDED_BY_AUGMENTATION) {
501                 ctx = ctx.getOriginalCtx();
502                 qNameModule = getModuleQNameByPrefix(ctx, prefix);
503             }
504             break;
505         }
506
507         Preconditions.checkArgument(qNameModule != null,
508                 "Error in module '%s': can not resolve QNameModule for '%s'. Statement source at %s",
509                 ctx.getRoot().rawStatementArgument(), value, ctx.getStatementSourceReference());
510         final QNameModule resultQNameModule;
511         if (qNameModule.getRevision() == null) {
512             resultQNameModule = QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV)
513                 .intern();
514         } else {
515             resultQNameModule = qNameModule;
516         }
517
518         return ctx.getFromNamespace(QNameCacheNamespace.class, QName.create(resultQNameModule, localName));
519     }
520
521     public static QNameModule getModuleQNameByPrefix(final StmtContext<?, ?, ?> ctx, final String prefix) {
522         final ModuleIdentifier modId = ctx.getRoot().getFromNamespace(ImpPrefixToModuleIdentifier.class, prefix);
523         final QNameModule qNameModule = ctx.getFromNamespace(ModuleIdentifierToModuleQName.class, modId);
524
525         if (qNameModule == null && StmtContextUtils.producesDeclared(ctx.getRoot(), SubmoduleStatement.class)) {
526             String moduleName = ctx.getRoot().getFromNamespace(BelongsToPrefixToModuleName.class, prefix);
527             return ctx.getFromNamespace(ModuleNameToModuleQName.class, moduleName);
528         }
529         return qNameModule;
530     }
531
532     public static QNameModule getRootModuleQName(final StmtContext<?, ?, ?> ctx) {
533         if (ctx == null) {
534             return null;
535         }
536
537         final StmtContext<?, ?, ?> rootCtx = ctx.getRoot();
538         final QNameModule qNameModule;
539
540         if (StmtContextUtils.producesDeclared(rootCtx, ModuleStatement.class)) {
541             qNameModule = rootCtx.getFromNamespace(ModuleCtxToModuleQName.class, rootCtx);
542         } else if (StmtContextUtils.producesDeclared(rootCtx, SubmoduleStatement.class)) {
543             final String belongsToModuleName = firstAttributeOf(rootCtx.substatements(), BelongsToStatement.class);
544             qNameModule = rootCtx.getFromNamespace(ModuleNameToModuleQName.class, belongsToModuleName);
545         } else {
546             qNameModule = null;
547         }
548
549         Preconditions.checkArgument(qNameModule != null, "Failed to look up root QNameModule for %s", ctx);
550         if (qNameModule.getRevision() != null) {
551             return qNameModule;
552         }
553
554         return QNameModule.create(qNameModule.getNamespace(), SimpleDateFormatUtil.DEFAULT_DATE_REV).intern();
555     }
556
557     @Nullable
558     public static StatementContextBase<?, ?, ?> findNode(final StmtContext<?, ?, ?> rootStmtCtx,
559             final SchemaNodeIdentifier node) {
560         return (StatementContextBase<?, ?, ?>) rootStmtCtx.getFromNamespace(SchemaNodeIdentifierBuildNamespace.class, node);
561     }
562
563     public static boolean isUnknownNode(final StmtContext<?, ?, ?> stmtCtx) {
564         return stmtCtx.getPublicDefinition().getDeclaredRepresentationClass()
565                 .isAssignableFrom(UnknownStatementImpl.class);
566     }
567
568     public static Deviation.Deviate parseDeviateFromString(final StmtContext<?, ?, ?> ctx, final String deviateKeyword) {
569         return Preconditions.checkNotNull(KEYWORD_TO_DEVIATE_MAP.get(deviateKeyword),
570                 "String '%s' is not valid deviate argument. Statement source at %s", deviateKeyword,
571                 ctx.getStatementSourceReference());
572     }
573
574     public static Status parseStatus(final String value) {
575         switch (value) {
576         case "current":
577             return Status.CURRENT;
578         case "deprecated":
579             return Status.DEPRECATED;
580         case "obsolete":
581             return Status.OBSOLETE;
582         default:
583             LOG.warn("Invalid 'status' statement: {}", value);
584             return null;
585         }
586     }
587
588     public static Date getLatestRevision(final Iterable<? extends StmtContext<?, ?, ?>> subStmts) {
589         Date revision = null;
590         for (StmtContext<?, ?, ?> subStmt : subStmts) {
591             if (subStmt.getPublicDefinition().getDeclaredRepresentationClass().isAssignableFrom(RevisionStatement
592                     .class)) {
593                 if (revision == null && subStmt.getStatementArgument() != null) {
594                     revision = (Date) subStmt.getStatementArgument();
595                 } else if (subStmt.getStatementArgument() != null && ((Date) subStmt.getStatementArgument()).compareTo
596                         (revision) > 0) {
597                     revision = (Date) subStmt.getStatementArgument();
598                 }
599             }
600         }
601         return revision;
602     }
603
604     public static boolean isModuleIdentifierWithoutSpecifiedRevision(final Object o) {
605         return (o instanceof ModuleIdentifier)
606                 && (((ModuleIdentifier) o).getRevision() == SimpleDateFormatUtil.DEFAULT_DATE_IMP || ((ModuleIdentifier) o)
607                         .getRevision() == SimpleDateFormatUtil.DEFAULT_BELONGS_TO_DATE);
608     }
609
610     /**
611      * Replaces illegal characters of QName by the name of the character (e.g.
612      * '?' is replaced by "QuestionMark" etc.).
613      *
614      * @param string
615      *            input String
616      * @return result String
617      */
618     public static String replaceIllegalCharsForQName(String string) {
619         string = LEFT_PARENTHESIS_MATCHER.replaceFrom(string, "LeftParenthesis");
620         string = RIGHT_PARENTHESIS_MATCHER.replaceFrom(string, "RightParenthesis");
621         string = AMPERSAND_MATCHER.replaceFrom(string, "Ampersand");
622         string = QUESTION_MARK_MATCHER.replaceFrom(string, "QuestionMark");
623
624         return string;
625     }
626
627     public static String fixUnicodeScriptPattern(String rawPattern) {
628         for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
629             try {
630                 Pattern.compile(rawPattern);
631                 return rawPattern;
632             } catch(PatternSyntaxException ex) {
633                 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
634                 if (ex.getMessage().contains("Unknown character script name")) {
635                     rawPattern = fixUnknownScripts(ex.getMessage(), rawPattern);
636                 } else {
637                     return rawPattern;
638                 }
639             }
640         }
641
642         LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
643         return rawPattern;
644     }
645
646     private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
647         StringBuilder result = new StringBuilder(rawPattern);
648         Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
649         if (matcher.find()) {
650             String capturedGroup = matcher.group(1);
651             if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
652                 int idx = rawPattern.indexOf("Is" + capturedGroup);
653                 result = result.replace(idx, idx + 2, "In");
654             }
655         }
656         return result.toString();
657     }
658
659     public static boolean belongsToTheSameModule(QName targetStmtQName, QName sourceStmtQName) {
660         if (targetStmtQName.getModule().equals(sourceStmtQName.getModule())) {
661             return true;
662         }
663         return false;
664     }
665
666     public static boolean isPresenceContainer(StatementContextBase<?, ?, ?> targetCtx) {
667         if (!targetCtx.getPublicDefinition().equals(Rfc6020Mapping.CONTAINER)) {
668             return false;
669         }
670
671         final List<StatementContextBase<?, ?, ?>> targetSubStatements = new ImmutableList.Builder<StatementContextBase<?, ?, ?>>()
672                 .addAll(targetCtx.declaredSubstatements()).addAll(targetCtx.effectiveSubstatements()).build();
673         for (final StatementContextBase<?, ?, ?> subStatement : targetSubStatements) {
674             if (subStatement.getPublicDefinition().equals(Rfc6020Mapping.PRESENCE)) {
675                 return true;
676             }
677         }
678
679         return false;
680     }
681 }