DCAE-D be initial commit
[sdc/dcae-d/dt-be-main.git] / dcaedt_validator / kwalify / src / main / java / kwalify / PlainYamlParser.java
1 /*
2  * copyright(c) 2005 kuwata-lab all rights reserved.
3  */
4
5 package kwalify;
6
7 import java.util.List;
8 import java.util.ArrayList;
9 import java.util.Map;
10 import java.util.HashMap;
11 import java.util.IdentityHashMap;
12 import java.util.regex.Matcher;
13 import java.util.Calendar;
14 import java.util.TimeZone;
15
16 /**
17  * plain yaml parser class which is a parent of YamlParser class.
18  */
19 public class PlainYamlParser implements Parser {
20
21     private static final String ANCHOR = "anchor '";
22     private static final String ENDFLAG_EOF       = "<EOF>";
23     private static final String ENDFLAG_DOC_BEGIN = "---";
24     private static final String ENDFLAG_DOC_END   = "...";
25     private static final String REGEXP1 = "^( *)(.*)";
26     private static final String REGEXP2 = "^((?::?[-.\\w]+|'.*?'|\".*?\"|=|<<) *):(( +)(.*))?$";
27
28     public static class Alias {
29         private String label;
30         private int lineNum;
31
32         Alias(String label, int lineNum) {
33             this.label = label;
34             this.lineNum = lineNum;
35         }
36
37         String getLabel() { return label; }
38
39         int getLineNumber() { return lineNum; }
40     }
41
42
43     private String[] lines;
44     private String line = null;
45     private int linenum = 0;
46     private Map<String,Object> anchors = new HashMap<>();
47     private Map<String,Integer> aliases = new HashMap<>();
48     private String endFlag = null;
49     private String sbuf = null;
50     private int index = 0;
51
52     PlainYamlParser(String yamlStr) {
53         List list = Util.toListOfLines(yamlStr);
54         int len = list.size();
55         lines = new String[len + 1];
56         for (int i = 0; i < len; i++) {
57             lines[i + 1] = (String)list.get(i);
58         }
59     }
60
61     public Object parse() throws SyntaxException {
62         Object data = parseChild(0);
63         if (data == null && endFlag.equals(ENDFLAG_DOC_BEGIN)) {
64             data = parseChild(0);
65         }
66         if (aliases.size() > 0) {
67             resolveAliases(data);
68         }
69         return data;
70     }
71
72     public boolean hasNext() {
73         return !endFlag.equals(ENDFLAG_EOF);
74     }
75
76     private List createSequence() {
77         return new ArrayList();
78     }
79
80     private void addSequenceValue(List seq, Object value) {
81         seq.add(value);
82     }
83
84     private void setSequenceValueAt(List seq, int index, Object value) {
85         seq.set(index, value);
86     }
87
88     Map createMapping() {
89         return new DefaultableHashMap();
90     }
91
92     private void setMappingValueWith(Map map, Object key, Object value) {
93         map.put(key, value);
94     }
95
96     void setMappingDefault(Map map, Object value) {
97         if (map instanceof Defaultable) {
98             ((Defaultable)map).setDefault(value);
99         }
100     }
101
102     private void mergeMapping(Map map, Map map2) {
103         for (Object key : map2.keySet()) {
104             if (!map.containsKey(key)) {
105                 Object value = map2.get(key);
106                 map.put(key, value);
107             }
108         }
109     }
110
111     private void mergeList(Map map, List maplist) throws SyntaxException {
112         for (Object elem : maplist) {
113             mergeCollection(map, elem);
114         }
115     }
116
117     private void mergeCollection(Map map, Object collection) throws SyntaxException {
118         if (collection instanceof Map) {
119             mergeMapping(map, (Map)collection);
120         } else if (collection instanceof List) {
121             mergeList(map, (List)collection);
122         } else {
123             throw syntaxError("'<<' requires collection (mapping, or sequence of mapping).");
124         }
125     }
126
127     private Object createScalar(Object value) {
128         return value;
129     }
130
131     private String currentLine() {
132         return line;
133     }
134
135     int currentLineNumber() {
136         return linenum;
137     }
138
139     protected String getLine() {
140         String currentLine;
141         do {
142             currentLine = getCurrentLine();
143         } while (currentLine != null && Util.matches(currentLine, "^\\s*($|#)"));
144         return currentLine;
145     }
146
147     private String getCurrentLine() {
148         if (++linenum < lines.length) {
149             line = lines[linenum];
150             if (Util.matches(line, "^\\.\\.\\.$")) {
151                 line = null;
152                 endFlag = ENDFLAG_DOC_END;
153             } else if (Util.matches(line, "^---( [!%].*)?$")) {
154                 line = null;
155                 endFlag = ENDFLAG_DOC_BEGIN;
156             }
157         } else {
158             line = null;
159             endFlag = ENDFLAG_EOF;
160         }
161         return line;
162     }
163
164     private void resetBuffer(String str) {
165         sbuf = str.charAt(str.length() - 1) == '\n' ? str : str + "\n";
166         index = -1;
167     }
168
169     private int getCurrentCharacter() {
170         if (index + 1 < sbuf.length()) {
171             index++;
172         } else {
173             String currentLine = getLine();
174             if (currentLine == null) {
175                 return -1;
176             }
177             resetBuffer(currentLine);
178             index++;
179         }
180         return sbuf.charAt(index);
181     }
182
183     private int getChar() {
184         int ch;
185         do {
186             ch = getCurrentCharacter();
187         } while (ch >= 0 && isWhite(ch));
188         return ch;
189     }
190
191     private int getCharOrNewline() {
192         int ch;
193         do {
194             ch = getCurrentCharacter();
195         } while (ch >= 0 && isWhite(ch) && ch != '\n');
196         return ch;
197     }
198
199     private int currentChar() {
200         return sbuf.charAt(index);
201     }
202
203     private SyntaxException syntaxError(String message, int linenum) {
204         return new YamlSyntaxException(message, linenum);
205     }
206
207     private SyntaxException syntaxError(String message) {
208         return new SyntaxException(message, linenum);
209     }
210
211     private Object parseChild(int column) throws SyntaxException {
212         String currentLine = getLine();
213         if (currentLine == null) {
214             return createScalar(null);
215         }
216         Matcher m = Util.matcher(currentLine, REGEXP1);
217         if (! m.find()) {
218             assert false;
219             return null;
220         }
221         int indent = m.group(1).length();
222         if (indent < column) {
223             return createScalar(null);
224         }
225         String value = m.group(2);
226         return parseValue(column, value, indent);
227     }
228
229     private Object parseValue(int column, String value, int valueStartColumn) throws SyntaxException {
230         Object data;
231         if        (Util.matches(value, "^-( |$)")) {
232             data = parseSequence(valueStartColumn, value);
233         } else if (Util.matches(value, REGEXP2)) {
234             data = parseMapping(valueStartColumn, value);
235         } else if (Util.matches(value, "^[\\[\\{]")) {
236             data = parseFlowStyle(value);
237         } else if (Util.matches(value, "^\\&[-\\w]+( |$)")) {
238             data = parseAnchor(column, value);
239         } else if (Util.matches(value, "^\\*[-\\w]+( |$)")) {
240             data = parseAlias(value);
241         } else if (Util.matches(value, "^[|>]")) {
242             data = parseBlockText(column, value);
243         } else if (Util.matches(value, "^!")) {
244             data = parseTag(column, value);
245         } else if (Util.matches(value, "^\\#")) {
246             data = parseChild(column);
247         } else {
248             data = parseScalar(value);
249         }
250         return data;
251     }
252
253     private static boolean isWhite(int ch) {
254         return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
255     }
256
257
258     private Object parseFlowStyle(String value) throws SyntaxException {
259         resetBuffer(value);
260         getChar();
261         Object data = parseFlow(0);
262         int ch = currentChar();
263         assert ch == ']' || ch == '}';
264         ch = getCharOrNewline();
265         if (ch != '\n' && ch != '#' && ch >= 0) {
266             throw syntaxError("flow style sequence is closed buf got '" + ((char)ch) + "'.");
267         }
268         if (ch >= 0) {
269             getLine();
270         }
271         return data;
272     }
273
274     private Object parseFlow(int depth) throws SyntaxException {
275         int ch = currentChar();
276         if (ch < 0) {
277             throw syntaxError("found EOF when parsing flow style.");
278         }
279         Object data;
280         if (ch == '[') {
281             data = parseFlowSequence(depth);
282         } else if (ch == '{') {
283             data = parseFlowMapping(depth);
284         } else {
285             data = parseFlowScalar();
286         }
287         return data;
288     }
289
290     private List parseFlowSequence(int depth) throws SyntaxException {
291         assert currentChar() == '[';
292         List seq = createSequence();
293         int ch = getChar();
294         if (ch != '}') {
295             addSequenceValue(seq, parseFlowSequenceItem(depth + 1));
296             while ((ch = currentChar()) == ',') {
297                 ch = getChar();
298                 if (ch == '}') {
299                     throw syntaxError("sequence item required (or last comma is extra).");
300                 }
301                 addSequenceValue(seq, parseFlowSequenceItem(depth + 1));
302             }
303         }
304         if (currentChar() != ']') {
305             throw syntaxError("flow style sequence requires ']'.");
306         }
307         if (depth > 0) {
308             getChar();
309         }
310         return seq;
311     }
312
313     private Object parseFlowSequenceItem(int depth) throws SyntaxException {
314         return parseFlow(depth);
315     }
316
317     private Map parseFlowMapping(int depth) throws SyntaxException {
318         assert currentChar() == '{';
319         Map map = createMapping();
320         int ch = getChar();
321         if (ch != '}') {
322             Object[] pair = parseFlowMappingItem(depth + 1);
323             Object key   = pair[0];
324             Object value = pair[1];
325             setMappingValueWith(map, key, value);
326             while ((ch = currentChar()) == ',') {
327                 ch = getChar();
328                 if (ch == '}') {
329                     throw syntaxError("mapping item required (or last comman is extra.");
330                 }
331                 pair = parseFlowMappingItem(depth + 1);
332                 key   = pair[0];
333                 value = pair[1];
334                 setMappingValueWith(map, key, value);
335             }
336         }
337         if (currentChar() != '}') {
338             throw syntaxError("flow style mapping requires '}'.");
339         }
340         if (depth > 0) {
341             getChar();
342         }
343         return map;
344     }
345
346     private Object[] parseFlowMappingItem(int depth) throws SyntaxException {
347         Object key = parseFlow(depth);
348         int ch = currentChar();
349         if (ch != ':') {
350             String s = ch >= 0 ? "'" + ((char)ch) + "'" : "EOF";
351             throw syntaxError("':' expected but got " + s);
352         }
353         getChar();
354         Object value = parseFlow(depth);
355         return new Object[] { key, value };
356     }
357
358     private Object parseFlowScalar() {
359         int ch = currentChar();
360         Object scalar;
361         StringBuilder sb = new StringBuilder();
362         if (ch == '"' || ch == '\'') {
363             int endch = ch;
364             while ((ch = getCurrentCharacter()) >= 0 && ch != endch) {
365                 sb.append((char)ch);
366             }
367             getChar();
368             scalar = sb.toString();
369         } else {
370             sb.append((char)ch);
371             while ((ch = getCurrentCharacter()) >= 0 && ch != ':' && ch != ',' && ch != ']' && ch != '}') {
372                 sb.append((char)ch);
373             }
374             scalar = toScalar(sb.toString().trim());
375         }
376         return createScalar(scalar);
377     }
378
379     private Object parseTag(int column, String value) throws SyntaxException {
380         assert Util.matches(value, "^!\\S+");
381         Matcher m = Util.matcher(value, "^!(\\S+)((\\s+)(.*))?$");
382         if (! m.find()) {
383             assert false;
384             return null;
385         }
386         String tag = m.group(1);
387         String space = m.group(3);
388         String value2 = m.group(4);
389         Object data;
390         if (value2 != null && value2.length() > 0) {
391             int valueStartColumn = column + 1 + tag.length() + space.length();
392             data = parseValue(column, value2, valueStartColumn);
393         } else {
394             data = parseChild(column);
395         }
396         return data;
397     }
398
399     private Object parseAnchor(int column, String value) throws SyntaxException {
400         assert Util.matches(value, "^\\&([-\\w]+)(( *)(.*))?$");
401         Matcher m = Util.matcher(value, "^\\&([-\\w]+)(( *)(.*))?$");
402         if (! m.find()) {
403             assert false;
404             return null;
405         }
406         String label  = m.group(1);
407         String space  = m.group(3);
408         String value2 = m.group(4);
409         Object data;
410         if (value2 != null && value2.length() > 0) {
411             int valueStartColumn = column + 1 + label.length() + space.length();
412             data = parseValue(column, value2, valueStartColumn);
413         } else {
414             data = parseChild(column);
415         }
416         registerAnchor(label, data);
417         return data;
418     }
419
420     private void registerAnchor(String label, Object data) throws SyntaxException {
421         if (anchors.containsKey(label)) {
422             throw syntaxError(ANCHOR + label + "' is already used.");
423         }
424         anchors.put(label, data);
425     }
426
427     private Object parseAlias(String value) throws SyntaxException {
428         assert value.matches("^\\*([-\\w]+)(( *)(.*))?$");
429         Matcher m = Util.matcher(value, "^\\*([-\\w]+)(( *)(.*))?$");
430         if (! m.find()) {
431             assert false;
432             return null;
433         }
434         String label  = m.group(1);
435         String value2 = m.group(4);
436         if (value2 != null && value2.length() > 0 && value2.charAt(0) != '#') {
437             throw syntaxError("alias cannot take any data.");
438         }
439         Object data = anchors.get(label);
440         if (data == null) {
441             data = registerAlias(label);
442         }
443         getLine();
444         return data;
445     }
446
447     private Alias registerAlias(String label) {
448         aliases.merge(label, 1, (a, b) -> a + b);
449         return new Alias(label, linenum);
450     }
451
452
453     private void resolveAliases(Object data) throws SyntaxException {
454         Map resolved = new IdentityHashMap();
455         resolveAliases(data, resolved);
456     }
457
458
459     private void resolveAliases(Object data, Map resolved) throws SyntaxException {
460         if (resolved.containsKey(data)) {
461             return;
462         }
463         resolved.put(data, data);
464         if (data instanceof List) {
465             resolveAliases((List)data, resolved);
466         } else if (data instanceof Map) {
467             resolveAliases((Map)data, resolved);
468         } else {
469             assert !(data instanceof Alias);
470         }
471         if (data instanceof Defaultable) {
472             Object defaultValue = ((Defaultable)data).getDefault();
473             if (defaultValue != null) {
474                 resolveAliases(defaultValue, resolved);
475             }
476         }
477     }
478
479     private void resolveAliases(List seq, Map resolved) throws SyntaxException {
480         int len = seq.size();
481         for (int i = 0; i < len; i++) {
482             Object val = seq.get(i);
483             if (val instanceof Alias) {
484                 Alias alias = (Alias)val;
485                 String label = alias.getLabel();
486                 if (anchors.containsKey(label)) {
487                     setSequenceValueAt(seq, i, anchors.get(label));
488                 } else {
489                     throw syntaxError(ANCHOR + alias.getLabel() + "' not found.");
490                 }
491             } else if (val instanceof List || val instanceof Map) {
492                 resolveAliases(val, resolved);
493             }
494         }
495     }
496
497     private void resolveAliases(Map map, Map resolved) throws SyntaxException {
498         for (Object key : map.keySet()) {
499             Object val = map.get(key);
500             if (val instanceof Alias) {
501                 Alias alias = (Alias) val;
502                 String label = alias.getLabel();
503                 if (anchors.containsKey(label)) {
504                     setMappingValueWith(map, key, anchors.get(label));
505                 } else {
506                     throw syntaxError(ANCHOR + alias.getLabel() + "' not found.", alias.getLineNumber());
507                 }
508             } else if (val instanceof List || val instanceof Map) {
509                 resolveAliases(val, resolved);
510             }
511         }
512     }
513
514     private Object parseBlockText(int column, String value) throws SyntaxException {
515         assert Util.matches(value, "^[>|]");
516         Matcher m = Util.matcher(value, "^([>|])([-+]?)(\\d*)\\s*(.*)$");
517         if (! m.find()) {
518             assert false;
519             return null;
520         }
521         char blockChar = m.group(1).length() > 0 ? m.group(1).charAt(0) : '\0';
522         char indicator = m.group(2).length() > 0 ? m.group(2).charAt(0) : '\0';
523         int indent     = m.group(3).length() > 0 ? Integer.parseInt(m.group(3)) : -1;
524         String text    = m.group(4);
525         char sep = blockChar == '|' ? '\n' : ' ';
526         String currentLine;
527         StringBuilder sb = new StringBuilder();
528         int n = 0;
529         while ((currentLine = getCurrentLine()) != null) {
530             m = Util.matcher(currentLine, "^( *)(.*)$");
531             m.find();
532             String space = m.group(1);
533             String str   = m.group(2);
534             if (indent < 0) {
535                 indent = space.length();
536             }
537             if (str.length() == 0) {
538                 n++;
539             } else {
540                 int slen = space.length();
541                 if (slen < column) {
542                     break;
543                 } else if (slen < indent) {
544                     throw syntaxError("invalid indent in block text.");
545                 } else {
546                     if (n > 0) {
547                         if (blockChar == '>' && sb.length() > 0) {
548                             sb.deleteCharAt(sb.length() - 1);
549                         }
550                         for (int i = 0; i < n; i++) {
551                             sb.append('\n');
552                         }
553                         n = 0;
554                     }
555                     str = currentLine.substring(indent);
556                 }
557             }
558             sb.append(str);
559             if ((blockChar == '>') && (sb.charAt(sb.length() - 1) == '\n')) {
560                     sb.setCharAt(sb.length() - 1, ' ');
561             }
562         }
563         if (currentLine != null && Util.matches(currentLine, "^ *#")) {
564             getLine();
565         }
566         switch (indicator) {
567         case '+':
568             handlePlus(blockChar, sb, n);
569             break;
570         case '-':
571             handleMinus(sep, sb);
572             break;
573         default:
574             if (blockChar == '>') {
575                 sb.setCharAt(sb.length() - 1, '\n');
576             }
577         }
578         return createScalar(text + sb.toString());
579     }
580
581     private void handleMinus(char sep, StringBuilder sb) {
582         if (sb.charAt(sb.length() - 1) == sep) {
583             sb.deleteCharAt(sb.length() - 1);
584         }
585     }
586
587     private void handlePlus(char blockChar, StringBuilder sb, int n) {
588         if (n > 0) {
589             if (blockChar == '>') {
590                 sb.setCharAt(sb.length() - 1, '\n');
591             }
592             for (int i = 0; i < n; i++) {
593                 sb.append('\n');
594             }
595         }
596     }
597
598
599     private List parseSequence(int column, String value) throws SyntaxException {
600         assert Util.matches(value, "^-(( +)(.*))?$");
601         List seq = createSequence();
602         while (true) {
603             Matcher m = Util.matcher(value, "^-(( +)(.*))?$");
604             if (! m.find()) {
605                 throw syntaxError("sequence item is expected.");
606             }
607             String space  = m.group(2);
608             String value2 = m.group(3);
609             int column2   = column + 1;
610
611             Object elem;
612             if (value2 == null || value2.length() == 0) {
613                 elem = parseChild(column2);
614             } else {
615                 int valueStartColumn = column2 + space.length();
616                 elem = parseValue(column2, value2, valueStartColumn);
617             }
618             addSequenceValue(seq, elem);
619
620             String currentLine = currentLine();
621             if (currentLine == null) {
622                 break;
623             }
624             Matcher m2 = Util.matcher(currentLine, REGEXP1);
625             m2.find();
626             int indent = m2.group(1).length();
627             if (indent < column) {
628                 break;
629             } else if (indent > column) {
630                 throw syntaxError("invalid indent of sequence.");
631             }
632             value = m2.group(2);
633         }
634         return seq;
635     }
636
637
638     private Map parseMapping(int column, String value) throws SyntaxException {
639         assert Util.matches(value, REGEXP2);
640         Map map = createMapping();
641         while (true) {
642             Matcher m = Util.matcher(value, REGEXP2);
643             if (! m.find()) {
644                 throw syntaxError("mapping item is expected.");
645             }
646             String v = m.group(1).trim();
647             Object key = toScalar(v);
648             String value2 = m.group(4);
649             int column2 = column + 1;
650
651             Object elem;
652             if (value2 == null || value2.length() == 0) {
653                 elem = parseChild(column2);
654             } else {
655                 int valueStartColumn = column2 + m.group(1).length() + m.group(3).length();
656                 elem = parseValue(column2, value2, valueStartColumn);
657             }
658             if ("=".equals(v)) {
659                 setMappingDefault(map, elem);
660             } else if ("<<".equals(v)) {
661                 mergeCollection(map, elem);
662             } else {
663                 setMappingValueWith(map, key, elem);
664             }
665
666             String currentLine = currentLine();
667             if (currentLine == null) {
668                 break;
669             }
670             Matcher m2 = Util.matcher(currentLine, REGEXP1);
671             m2.find();
672             int indent = m2.group(1).length();
673             if (indent < column) {
674                 break;
675             } else if (indent > column) {
676                 throw syntaxError("invalid indent of mapping.");
677             }
678             value = m2.group(2);
679         }
680         return map;
681     }
682
683
684     private Object parseScalar(String value) {
685         Object data = createScalar(toScalar(value));
686         getLine();
687         return data;
688     }
689
690
691     private Object toScalar(String value) {
692         Matcher m;
693         if ((m = Util.matcher(value, "^\"(.*)\"([ \t]*#.*$)?")).find()) {
694             return m.group(1);
695         } else if ((m = Util.matcher(value, "^'(.*)'([ \t]*#.*$)?")).find()) {
696             return m.group(1);
697         } else if ((m = Util.matcher(value, "^(.*\\S)[ \t]*#")).find()) {
698             value = m.group(1);
699         }
700
701         if (Util.matches(value, "^-?0x\\d+$")) {
702             return Integer.parseInt(value, 16);
703         } else if (Util.matches(value, "^-?0\\d+$")) {
704             return Integer.parseInt(value, 8);
705         } else if (Util.matches(value, "^-?\\d+$")) {
706             return Integer.parseInt(value, 10);
707         } else if (Util.matches(value, "^-?\\d+\\.\\d+$")) {
708             return Double.parseDouble(value);
709         } else if (Util.matches(value, "^(true|yes|on)$")) {
710             return Boolean.TRUE;
711         } else if (Util.matches(value, "^(false|no|off)$")) {
712             return Boolean.FALSE;
713         } else if (Util.matches(value, "^(null|~)$")){
714             return null;
715         } else if (Util.matches(value, "^:(\\w+)$"))       {
716             return value;
717         } else if ((m = Util.matcher(value, "^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)$")).find()) {
718             int year  = Integer.parseInt(m.group(1));
719             int month = Integer.parseInt(m.group(2));
720             int day   = Integer.parseInt(m.group(3));
721             Calendar cal = Calendar.getInstance();
722             cal.set(year, month, day, 0, 0, 0);
723             return cal.getTime();
724         } else if ((m = Util.matcher(value, "^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)(?:[Tt]|[ \t]+)(\\d\\d?):(\\d\\d):(\\d\\d)(\\.\\d*)?(?:Z|[ \t]*([-+]\\d\\d?)(?::(\\d\\d))?)?$")).find()) {
725             int year    = Integer.parseInt(m.group(1));
726             int month   = Integer.parseInt(m.group(2));
727             int day     = Integer.parseInt(m.group(3));
728             int hour    = Integer.parseInt(m.group(4));
729             int min     = Integer.parseInt(m.group(5));
730             int sec     = Integer.parseInt(m.group(6));
731
732             String timezone = "GMT" + m.group(8) + ":" + m.group(9);
733             Calendar cal = Calendar.getInstance();
734             cal.set(year, month, day, hour, min, sec);
735             cal.setTimeZone(TimeZone.getTimeZone(timezone));
736             return cal.getTime();
737         } else {
738             return value;
739         }
740     }
741
742 }