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