Update unicode blocks for Java 11
[yangtools.git] / parser / yang-parser-rfc7950 / src / main / java / org / opendaylight / yangtools / yang / parser / rfc7950 / stmt / pattern / RegexUtils.java
1 /*
2  * Copyright (c) 2017 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.rfc7950.stmt.pattern;
9
10 import com.google.common.collect.ImmutableSet;
11 import java.lang.Character.UnicodeBlock;
12 import java.util.regex.Matcher;
13 import java.util.regex.Pattern;
14 import java.util.regex.PatternSyntaxException;
15 import org.slf4j.Logger;
16 import org.slf4j.LoggerFactory;
17
18 /**
19  * Utilities for converting YANG XSD regexes into Java-compatible regexes.
20  */
21 final class RegexUtils {
22     private static final Logger LOG = LoggerFactory.getLogger(RegexUtils.class);
23     private static final Pattern BETWEEN_CURLY_BRACES_PATTERN = Pattern.compile("\\{(.+?)\\}");
24
25     /**
26      * Unicode blocks known to Java. We do not use {@link UnicodeBlock#forName(String)} due to the need to differentiate
27      * runtime-supported and compile-time supported blocks. We are limited to the latter, i.e. even if we are running
28      * on (for example) Java 17, we must rely only on blocks supported by our compilation target (for example) Java 11.
29      *
30      * <p>
31      * Furthermore we take a page from
32      * <a href="https://www.w3.org/TR/xmlschema11-2/#charcter-classes">G.4.2.3 Block escapes</a> and only match properly
33      * normalized names, which is different from what Java does.
34      */
35     private static final ImmutableSet<String> JAVA_UNICODE_BLOCKS = ImmutableSet.<String>builder()
36         // Java 7 and earlier
37         .add("AegeanNumbers")
38         .add("AlchemicalSymbols")
39         .add("AlphabeticPresentationForms")
40         .add("AncientGreekMusicalNotation")
41         .add("AncientGreekNumbers")
42         .add("AncientSymbols")
43         .add("Arabic")
44         .add("ArabicPresentationForms-A")
45         .add("ArabicPresentationForms-B")
46         .add("ArabicSupplement")
47         .add("Armenian")
48         .add("Arrows")
49         .add("Avestan")
50         .add("Balinese")
51         .add("Bamum")
52         .add("BamumSupplement")
53         .add("BasicLatin")
54         .add("Batak")
55         .add("Bengali")
56         .add("BlockElements")
57         .add("Bopomofo")
58         .add("BopomofoExtended")
59         .add("BoxDrawing")
60         .add("Brahmi")
61         .add("BraillePatterns")
62         .add("Buginese")
63         .add("Buhid")
64         .add("ByzantineMusicalSymbols")
65         .add("Carian")
66         .add("Cham")
67         .add("Cherokee")
68         .add("CJKCompatibility")
69         .add("CJKCompatibilityForms")
70         .add("CJKCompatibilityIdeographs")
71         .add("CJKCompatibilityIdeographsSupplement")
72         .add("CJKRadicalsSupplement")
73         .add("CJKStrokes")
74         .add("CJKSymbolsandPunctuation")
75         .add("CJKUnifiedIdeographs")
76         .add("CJKUnifiedIdeographsExtensionA")
77         .add("CJKUnifiedIdeographsExtensionB")
78         .add("CJKUnifiedIdeographsExtensionC")
79         .add("CJKUnifiedIdeographsExtensionD")
80         .add("CombiningDiacriticalMarks")
81         .add("CombiningDiacriticalMarksSupplement")
82         .add("CombiningHalfMarks")
83         .add("CombiningDiacriticalMarksforSymbols")
84         .add("CommonIndicNumberForms")
85         .add("ControlPictures")
86         .add("Coptic")
87         .add("CountingRodNumerals")
88         .add("Cuneiform")
89         .add("CuneiformNumbersandPunctuation")
90         .add("CurrencySymbols")
91         .add("CypriotSyllabary")
92         .add("Cyrillic")
93         .add("CyrillicExtended-A")
94         .add("CyrillicExtended-B")
95         .add("CyrillicSupplementary")
96         .add("Deseret")
97         .add("Devanagari")
98         .add("DevanagariExtended")
99         .add("Dingbats")
100         .add("DominoTiles")
101         .add("EgyptianHieroglyphs")
102         .add("Emoticons")
103         .add("EnclosedAlphanumericSupplement")
104         .add("EnclosedAlphanumerics")
105         .add("EnclosedCJKLettersandMonths")
106         .add("EnclosedIdeographicSupplement")
107         .add("Ethiopic")
108         .add("EthiopicExtended")
109         .add("EthiopicExtended-A")
110         .add("EthiopicSupplement")
111         .add("GeneralPunctuation")
112         .add("GeometricShapes")
113         .add("Georgian")
114         .add("GeorgianSupplement")
115         .add("Glagolitic")
116         .add("Gothic")
117         .add("GreekandCoptic")
118         .add("GreekExtended")
119         .add("Gujarati")
120         .add("Gurmukhi")
121         .add("HalfwidthandFullwidthForms")
122         .add("HangulCompatibilityJamo")
123         .add("HangulJamo")
124         .add("HangulJamoExtended-A")
125         .add("HangulJamoExtended-B")
126         .add("HangulSyllables")
127         .add("Hanunoo")
128         .add("Hebrew")
129         .add("HighPrivateUseSurrogates")
130         .add("HighSurrogates")
131         .add("Hiragana")
132         .add("IdeographicDescriptionCharacters")
133         .add("ImperialAramaic")
134         .add("InscriptionalPahlavi")
135         .add("InscriptionalParthian")
136         .add("IPAExtensions")
137         .add("Javanese")
138         .add("Kaithi")
139         .add("KanaSupplement")
140         .add("Kanbun")
141         .add("Kangxi Radicals")
142         .add("Kannada")
143         .add("Katakana")
144         .add("KatakanaPhoneticExtensions")
145         .add("KayahLi")
146         .add("Kharoshthi")
147         .add("Khmer")
148         .add("KhmerSymbols")
149         .add("Lao")
150         .add("Latin-1Supplement")
151         .add("LatinExtended-A")
152         .add("LatinExtendedAdditional")
153         .add("LatinExtended-B")
154         .add("LatinExtended-C")
155         .add("LatinExtended-D")
156         .add("Lepcha")
157         .add("LetterlikeSymbols")
158         .add("Limbu")
159         .add("LinearBIdeograms")
160         .add("LinearBSyllabary")
161         .add("Lisu")
162         .add("LowSurrogates")
163         .add("Lycian")
164         .add("Lydian")
165         .add("MahjongTiles")
166         .add("Malayalam")
167         .add("Mandaic")
168         .add("MathematicalAlphanumericSymbols")
169         .add("MathematicalOperators")
170         .add("MeeteiMayek")
171         .add("MiscellaneousMathematicalSymbols-A")
172         .add("MiscellaneousMathematicalSymbols-B")
173         .add("MiscellaneousSymbols")
174         .add("MiscellaneousSymbolsandArrows")
175         .add("MiscellaneousSymbolsAndPictographs")
176         .add("MiscellaneousTechnical")
177         .add("ModifierToneLetters")
178         .add("Mongolian")
179         .add("MusicalSymbols")
180         .add("Myanmar")
181         .add("MyanmarExtended-A")
182         .add("NewTaiLue")
183         .add("NKo")
184         .add("NumberForms")
185         .add("Ogham")
186         .add("OlChiki")
187         .add("OldItalic")
188         .add("OldPersian")
189         .add("OldSouthArabian")
190         .add("OldTurkic")
191         .add("OpticalCharacterRecognition")
192         .add("Oriya")
193         .add("Osmanya")
194         .add("Phags-pa")
195         .add("PhaistosDisc")
196         .add("Phoenician")
197         .add("PhoneticExtensions")
198         .add("PhoneticExtensionsSupplement")
199         .add("PlayingCards")
200         .add("PrivateUseArea")
201         .add("Rejang")
202         .add("RumiNumeralSymbols")
203         .add("Runic")
204         .add("Samaritan")
205         .add("Saurashtra")
206         .add("Shavian")
207         .add("Sinhala")
208         .add("SmallFormVariants")
209         .add("SpacingModifierLetters")
210         .add("Specials")
211         .add("Sundanese")
212         .add("SuperscriptsandSubscripts")
213         .add("SupplementalArrows-A")
214         .add("SupplementalArrows-B")
215         .add("SupplementalMathematicalOperators")
216         .add("SupplementalPunctuation")
217         .add("SupplementaryPrivateUseArea-A")
218         .add("SupplementaryPrivateUseArea-B")
219         .add("SylotiNagri")
220         .add("Syriac")
221         .add("Tagalog")
222         .add("Tagbanwa")
223         .add("Tags")
224         .add("TaiLe")
225         .add("TaiTham")
226         .add("TaiViet")
227         .add("TaiXuanJingSymbols")
228         .add("Tamil")
229         .add("Telugu")
230         .add("Thaana")
231         .add("Thai")
232         .add("Tibetan")
233         .add("Tifinagh")
234         .add("TransportAndMapSymbols")
235         .add("Ugaritic")
236         .add("UnifiedCanadianAboriginalSyllabics")
237         .add("UnifiedCanadianAboriginalSyllabicsExtended")
238         .add("Vai")
239         .add("VariationSelectors")
240         .add("VariationSelectorsSupplement")
241         .add("VedicExtensions")
242         .add("VerticalForms")
243         .add("YiRadicals")
244         .add("YiSyllables")
245         .add("YijingHexagramSymbols")
246
247         // Java 8:
248         .add("ArabicExtended-A")
249         .add("ArabicMathematicalAlphabeticSymbols")
250         .add("Chakma")
251         .add("MeeteiMeyekExtensions")
252         .add("MeroiticCursive")
253         .add("MeroiticHieroglyphs")
254         .add("Miao")
255         .add("Sharada")
256         .add("SoraSompeng")
257         .add("SundaneseSupplement")
258         .add("Takri")
259
260         // Java 9:
261         .add("Ahom")
262         .add("AnatolianHieroglyphs")
263         .add("BassaVah")
264         .add("CaucasianAlbanian")
265         .add("CherokeeSupplement")
266         .add("CJKUnifiedIdeographsExtensionE")
267         .add("CombiningDiacriticalMarksExtended")
268         .add("CopticEpactNumbers")
269         .add("Duployan")
270         .add("EarlyDynasticCuneiform")
271         .add("Elbasan")
272         .add("GeometricShapesExtended")
273         .add("Grantha")
274         .add("Hatran")
275         .add("Khojki")
276         .add("Khudawadi")
277         .add("LatinExtended-E")
278         .add("LinearA")
279         .add("Mahajani")
280         .add("Manichaean")
281         .add("MendeKikakui")
282         .add("Modi")
283         .add("Mro")
284         .add("Multani")
285         .add("MyanmarExtended-B")
286         .add("Nabataean")
287         .add("OldHungarian")
288         .add("OldNorthArabian")
289         .add("OldPermic")
290         .add("OrnamentalDingbats")
291         .add("PahawhHmong")
292         .add("Palmyrene")
293         .add("PauCinHau")
294         .add("PsalterPahlavi")
295         .add("ShorthandFormatControls")
296         .add("Siddham")
297         .add("SinhalaArchaicNumbers")
298         .add("SupplementalArrows-C")
299         .add("SupplementalSymbolsandPictographs")
300         .add("SuttonSignWriting")
301         .add("Tirhuta")
302         .add("WarangCiti")
303
304         // Java 11
305         .add("Adlam")
306         .add("Bhaiksuki")
307         .add("CJKUnifiedIdeographsExtensionF")
308         .add("CyrillicExtended-C")
309         .add("GlagoliticSupplement")
310         .add("IdeographicSymbolsandPunctuation")
311         .add("KanaExtended-A")
312         .add("Marchen")
313         .add("MasaramGondi")
314         .add("MongolianSupplement")
315         .add("Newa")
316         .add("Nushu")
317         .add("Osage")
318         .add("Soyombo")
319         .add("SyriacSupplement")
320         .add("Tangut")
321         .add("TangutComponents")
322         .add("ZanabazarSquare")
323         .build();
324
325     private static final int UNICODE_SCRIPT_FIX_COUNTER = 30;
326
327     private RegexUtils() {
328         // Hidden on purpose
329     }
330
331     /**
332      * Converts XSD regex to Java-compatible regex.
333      *
334      * @param xsdRegex XSD regex pattern as it is defined in a YANG source
335      * @return Java-compatible regex
336      */
337     static String getJavaRegexFromXSD(final String xsdRegex) {
338         // Note: we are using a non-capturing group to deal with internal structure issues, like branches and similar.
339         return "^(?:" + fixUnicodeScriptPattern(escapeChars(xsdRegex)) + ")$";
340     }
341
342     /*
343      * As both '^' and '$' are special anchor characters in java regular
344      * expressions which are implicitly present in XSD regular expressions,
345      * we need to escape them in case they are not defined as part of
346      * character ranges i.e. inside regular square brackets.
347      */
348     private static String escapeChars(final String regex) {
349         final StringBuilder result = new StringBuilder(regex.length());
350         int bracket = 0;
351         boolean escape = false;
352         for (int i = 0; i < regex.length(); i++) {
353             final char ch = regex.charAt(i);
354             switch (ch) {
355                 case '[':
356                     if (!escape) {
357                         bracket++;
358                     }
359                     escape = false;
360                     result.append(ch);
361                     break;
362                 case ']':
363                     if (!escape) {
364                         bracket--;
365                     }
366                     escape = false;
367                     result.append(ch);
368                     break;
369                 case '\\':
370                     escape = !escape;
371                     result.append(ch);
372                     break;
373                 case '^':
374                 case '$':
375                     if (bracket == 0) {
376                         result.append('\\');
377                     }
378                     escape = false;
379                     result.append(ch);
380                     break;
381                 default:
382                     escape = false;
383                     result.append(ch);
384             }
385         }
386         return result.toString();
387     }
388
389     private static String fixUnicodeScriptPattern(String rawPattern) {
390         for (int i = 0; i < UNICODE_SCRIPT_FIX_COUNTER; i++) {
391             try {
392                 Pattern.compile(rawPattern);
393                 return rawPattern;
394             } catch (final PatternSyntaxException ex) {
395                 LOG.debug("Invalid regex pattern syntax in: {}", rawPattern, ex);
396                 final String msg = ex.getMessage();
397                 if (msg.startsWith("Unknown character script name")
398                         || msg.startsWith("Unknown character property name")) {
399                     rawPattern = fixUnknownScripts(msg, rawPattern);
400                 } else {
401                     return rawPattern;
402                 }
403             }
404         }
405
406         LOG.warn("Regex pattern could not be fixed: {}", rawPattern);
407         return rawPattern;
408     }
409
410     private static String fixUnknownScripts(final String exMessage, final String rawPattern) {
411         StringBuilder result = new StringBuilder(rawPattern);
412         final Matcher matcher = BETWEEN_CURLY_BRACES_PATTERN.matcher(exMessage);
413         if (matcher.find()) {
414             String capturedGroup = matcher.group(1);
415             if (capturedGroup.startsWith("In/Is")) {
416                 // Java 9 changed the reporting string
417                 capturedGroup = capturedGroup.substring(5);
418             } else if (capturedGroup.startsWith("Is")) {
419                 // Java 14 changed the reporting string (https://bugs.openjdk.java.net/browse/JDK-8230338)
420                 capturedGroup = capturedGroup.substring(2);
421             }
422
423             if (JAVA_UNICODE_BLOCKS.contains(capturedGroup)) {
424                 final int idx = rawPattern.indexOf("Is" + capturedGroup);
425                 result = result.replace(idx, idx + 2, "In");
426             }
427         }
428         return result.toString();
429     }
430 }