1 package net.avcompris.commons3.yaml;
2
3 import static com.google.common.base.Preconditions.checkNotNull;
4 import static com.google.common.base.Preconditions.checkState;
5 import static com.google.common.collect.Lists.newArrayList;
6 import static com.google.common.collect.Maps.newHashMap;
7 import static net.avcompris.commons3.yaml.YamlReader.Token.CLOSE_CURLY_BRACKET;
8 import static net.avcompris.commons3.yaml.YamlReader.Token.CLOSE_SQUARE_BRACKET;
9 import static net.avcompris.commons3.yaml.YamlReader.Token.COLON;
10 import static net.avcompris.commons3.yaml.YamlReader.Token.COMMA;
11 import static net.avcompris.commons3.yaml.YamlReader.Token.HYPHEN;
12 import static net.avcompris.commons3.yaml.YamlReader.Token.LINEBREAK;
13 import static net.avcompris.commons3.yaml.YamlReader.Token.OPEN_CURLY_BRACKET;
14 import static net.avcompris.commons3.yaml.YamlReader.Token.OPEN_SQUARE_BRACKET;
15
16 import java.io.IOException;
17 import java.io.Reader;
18 import java.util.List;
19 import java.util.Map;
20
21 import org.apache.commons.lang3.NotImplementedException;
22
23 import net.avcompris.commons3.yaml.YamlReader.Token;
24
25 final class YamlLoader {
26
27 private static final Logger logger = LoggerFactory.getLogger(YamlLoader.class);
28
29 public Map<Object, Object> load(
30 final Reader reader
31 ) throws IOException {
32
33 checkNotNull(reader, "reader");
34
35 final Map<Object, Object> map = newHashMap();
36
37 final YamlReader yamlReader = new YamlReader(reader);
38
39 while (true) {
40
41 final Token token0 = yamlReader.readNextToken(false);
42
43 logger.debug("main, token0: " + token0);
44
45 if (token0 == null) {
46
47 break;
48 }
49
50 if (token0 == OPEN_CURLY_BRACKET) {
51
52 final Map<Object, Object> map2 = readMapWithClosingBracket(yamlReader);
53
54 final Token token1 = yamlReader.readNextToken(false);
55
56 if (token1 == null) {
57
58 map.putAll(map2);
59
60 } else if (token1 == COLON) {
61
62
63
64 final Object value = readValue(yamlReader);
65
66 checkState(!map.containsKey(map2), "Duplicate key: " + map2);
67
68 map.put(map2, value);
69
70 } else {
71
72 throw new YamlReaderException(
73 yamlReader.getCurrentLineNumber(),
74 yamlReader.getCurrentLineNumber(),
75 "Illegal token: \"" + token1 + "\"");
76 }
77
78 } else if (token0 == LINEBREAK) {
79
80 continue;
81
82 } else if (token0 == HYPHEN) {
83
84 throw new NotImplementedException("List at 0-level.");
85
86 } else {
87
88 final Object key = toObject(token0);
89
90 final Token token1 = yamlReader.readNextToken(false);
91
92 logger.debug("main, token1: " + token1);
93
94 if (token1 != COLON) {
95
96 throw new YamlKeyException(yamlReader.getCurrentLineNumber(),
97 yamlReader.getCurrentColumnNumber(),
98 "Illegal key: \"" + token0 + "+" + token1 + "\"");
99 }
100
101 final Object value = readValue(yamlReader);
102
103 checkState(!map.containsKey(key), "Duplicate key: " + key);
104
105 map.put(key, value);
106 }
107 }
108
109 return map;
110 }
111
112 private static Object readValue(
113 final YamlReader yamlReader
114 ) throws IOException {
115
116 logger.debug("readValue()...");
117
118 final Token token0 = yamlReader.readNextToken(false);
119
120 logger.debug("readValue(), token: " + token0);
121
122 if (token0 == OPEN_CURLY_BRACKET) {
123
124 return readMapWithClosingBracket(yamlReader);
125
126 } else if (token0 == OPEN_SQUARE_BRACKET) {
127
128 return readArrayWithClosingBracket(yamlReader);
129
130 } else if (token0 == LINEBREAK) {
131
132 final int indent = yamlReader.readIndent();
133
134 return readValueWithIndent(yamlReader, indent);
135
136 } else {
137
138 final Object value0 = toObject(token0);
139
140 if (value0 instanceof String || value0 instanceof Integer) {
141
142 final StringBuilder sb = new StringBuilder(value0.toString());
143
144 while (true) {
145
146 final Token token1 = yamlReader.readNextToken(false);
147
148 if (token1 == null || token1 == LINEBREAK) {
149
150 break;
151 }
152
153 final Object value1 = toObject(token1);
154
155 if (value1 instanceof String) {
156
157 sb.append(value1.toString());
158
159 } else {
160
161 throw new NotImplementedException("value1: " + value1);
162 }
163 }
164
165 return sb.toString();
166
167 } else {
168
169 return value0;
170 }
171 }
172 }
173
174 private static Object toObject(
175 final Token token
176 ) {
177
178 checkNotNull(token, "token");
179
180 if (token.isQuoted) {
181
182 return token.s;
183
184 } else {
185
186 if ("true".contentEquals(token.s)) {
187
188 return Boolean.TRUE;
189
190 } else if ("false".contentEquals(token.s)) {
191
192 return Boolean.FALSE;
193 }
194 try {
195
196 return Integer.parseInt(token.s);
197
198 } catch (final NumberFormatException parseIntE) {
199
200 try {
201
202 return Long.parseLong(token.s);
203
204 } catch (final NumberFormatException parseLongE) {
205
206 return token.s;
207 }
208 }
209 }
210 }
211
212 private static Map<Object, Object> readMapWithClosingBracket(
213 final YamlReader yamlReader
214 ) throws IOException {
215
216 final Map<Object, Object> map = newHashMap();
217
218 while (true) {
219
220 final Token token0 = yamlReader.readNextToken(false);
221
222 if (token0 != null) {
223
224 if (token0 == CLOSE_CURLY_BRACKET) {
225
226 return map;
227 }
228
229 final Object key = toObject(token0);
230
231 final Token token1 = yamlReader.readNextToken(false);
232
233 if (token1 != COLON) {
234
235 throw new YamlKeyException(
236 yamlReader.getCurrentTokenLineNumber(),
237 yamlReader.getCurrentTokenColumnNumber(),
238 "Illegal key: " + token0 + "+" + token1);
239 }
240
241 final Token token2 = yamlReader.readNextToken(true);
242
243 final Object value = toObject(token2);
244
245 checkState(!map.containsKey(key), "Duplicate key: " + key);
246
247 map.put(key, value);
248
249 final Token token3 = yamlReader.readNextToken(true);
250
251 if (token3 == CLOSE_CURLY_BRACKET || token3 == null) {
252
253 break;
254
255 } else if (token3 == COMMA) {
256
257 continue;
258
259 } else {
260
261 throw new NotImplementedException("token3: " + token3);
262 }
263 }
264 }
265
266 return map;
267 }
268
269 private static List<Object> readArrayWithClosingBracket(
270 final YamlReader yamlReader
271 ) throws IOException {
272
273 logger.debug("readArrayWithClosingBracket()...");
274
275 final List<Object> list = newArrayList();
276
277 while (true) {
278
279 final Token token0 = yamlReader.readNextToken(true);
280
281 if (token0 != null) {
282
283 if (token0 == CLOSE_SQUARE_BRACKET) {
284
285 return list;
286 }
287
288 if (token0 == COMMA) {
289
290 throw new YamlReaderException(
291 yamlReader.getCurrentTokenLineNumber(),
292 yamlReader.getCurrentTokenColumnNumber(),
293 "Illegal token: " + token0);
294 }
295
296 final Object item = toObject(token0);
297
298 list.add(item);
299
300 final Token token1 = yamlReader.readNextToken(true);
301
302 if (token1 == CLOSE_SQUARE_BRACKET) {
303
304 break;
305 }
306
307 if (token1 != COMMA) {
308
309 throw new YamlKeyException(
310 yamlReader.getCurrentTokenLineNumber(),
311 yamlReader.getCurrentTokenColumnNumber(),
312 "Illegal token: " + token1);
313 }
314 }
315 }
316
317 return list;
318 }
319
320 private static Object readValueWithIndent(
321 final YamlReader yamlReader,
322 final int indent
323 ) throws IOException {
324
325 logger.debug("readValueWithIndent(" + indent + ")...");
326
327 final Map<Object, Object> map = newHashMap();
328
329 while (true) {
330
331 if (yamlReader.readIndent() < indent) {
332
333 break;
334 }
335
336 final Token token0 = yamlReader.readNextToken(false);
337
338 logger.debug(" token0: " + token0);
339
340 if (token0 == LINEBREAK) {
341
342 continue;
343 }
344
345 if (token0 == null) {
346
347 break;
348 }
349
350 if (token0 == HYPHEN) {
351
352 final Object array = readArrayWithIndent(yamlReader, indent);
353
354 return array;
355 }
356
357
358
359
360
361
362 final Token token1 = yamlReader.readNextToken(false);
363
364 logger.debug(" token1: " + token1);
365
366 if (token1 == null || token1 == LINEBREAK) {
367
368 final int indent2 = yamlReader.readIndent();
369
370 if (indent2 < indent) {
371
372 final Object value = toObject(token0);
373
374 return value;
375 }
376 }
377
378 final Object key = toObject(token0);
379
380 if (token1 != COLON) {
381
382 throw new YamlKeyException(
383 yamlReader.getCurrentTokenLineNumber(),
384 yamlReader.getCurrentTokenColumnNumber(),
385 "Illegal key: " + token0 + "+" + token1);
386 }
387
388 final Token token2 = yamlReader.readNextToken(false);
389
390 if (token2 == LINEBREAK) {
391
392 final int indent3 = yamlReader.readIndent();
393
394 if (indent3 <= indent) {
395
396 throw new YamlReaderException(
397 yamlReader.getCurrentLineNumber(),
398 yamlReader.getCurrentTokenColumnNumber(),
399 "Unterminated colon");
400
401 } else {
402
403 final Object value = readValueWithIndent(yamlReader, indent3);
404
405 checkState(!map.containsKey(key), "Duplicate key: " + key);
406
407 map.put(key, value);
408
409 final int indent4 = yamlReader.readIndent();
410
411 if (indent4 < indent) {
412
413 break;
414 }
415
416 continue;
417 }
418
419 } else if (token2 == OPEN_SQUARE_BRACKET) {
420
421 final Object value2 = readArrayWithClosingBracket(yamlReader);
422
423 map.put(key, value2);
424
425 continue;
426 }
427
428 checkState(!map.containsKey(key), "Duplicate key: " + key);
429
430 final Object value2 = toObject(token2);
431
432 final StringBuilder sb;
433
434 final boolean appendable;
435
436 if (value2 instanceof String) {
437
438 sb = new StringBuilder(value2.toString());
439
440 appendable = true;
441
442 } else {
443
444 sb = null;
445
446 appendable = false;
447 }
448
449 Token token3 = null;
450
451 while (true) {
452
453 token3 = yamlReader.readNextToken(false);
454
455 if (token3 == null || token3 == LINEBREAK) {
456
457 break;
458 }
459
460 checkState(appendable, "cannot append to value: " + value2);
461
462 final Object value3 = toObject(token3);
463
464 if (value3 instanceof String) {
465
466 sb.append(value3.toString());
467
468 } else {
469
470 throw new NotImplementedException("value3: " + value3);
471 }
472 }
473
474 map.put(key, appendable ? sb.toString() : value2);
475
476 if (token3 == null) {
477
478 break;
479
480
481 }
482
483 if (token3 == LINEBREAK) {
484
485 final int indent3 = yamlReader.readIndent();
486
487 if (indent3 == -1) {
488
489 break;
490 }
491
492 if (indent3 == indent) {
493
494 continue;
495 }
496
497
498
499
500
501 if (indent3 < indent) {
502
503 break;
504 }
505
506 continue;
507 }
508
509 throw new NotImplementedException("token3: " + token3);
510 }
511
512 return map;
513 }
514
515 private static Object readArrayWithIndent(
516 final YamlReader yamlReader,
517 final int indent
518 ) throws IOException {
519
520 logger.debug("readArrayWithIndent()...");
521
522 final List<Object> list = newArrayList();
523
524 while (true) {
525
526 if (yamlReader.readIndent() < indent) {
527
528 break;
529 }
530
531 final Token token0 = yamlReader.readNextToken(false);
532
533 if (!list.isEmpty() && token0 == HYPHEN) {
534
535 continue;
536 }
537
538 final int currentIndent = yamlReader.getCurrentTokenColumnNumber() - 2;
539
540 logger.debug("readArrayWithIndent(), token0: " + token0);
541
542 if (token0 == null) {
543
544 throw new YamlReaderException(
545 yamlReader.getCurrentTokenLineNumber(),
546 yamlReader.getCurrentTokenColumnNumber(),
547 "Unterminated hyphen");
548 }
549
550 final Token token1 = yamlReader.readNextToken(false);
551
552 if (token1 == null || token1 == LINEBREAK) {
553
554 list.add(toObject(token0));
555
556 final int indent2 = currentIndent;
557
558 if (indent2 < indent) {
559
560 break;
561 }
562
563 continue;
564 }
565
566 if (token1 == COLON) {
567
568 final Token token2 = yamlReader.readNextToken(false);
569
570 if (token2 == LINEBREAK) {
571
572 final Object value = readValueWithIndent(yamlReader, yamlReader.readIndent());
573
574 final Map<Object, Object> map = newHashMap();
575
576 map.put(toObject(token0), value);
577
578 list.add(map);
579
580
581
582 continue;
583
584 }
585
586 final Token token3 = yamlReader.readNextToken(false);
587
588 checkState(token3 == LINEBREAK, "token3 should be LINEBREAK, but was: " + token3);
589
590 final Object value = readValueWithIndent(yamlReader, indent + 2);
591
592 if (List.class.isInstance(value)) {
593
594 @SuppressWarnings("unchecked")
595 final List<Object> l = (List<Object>) value;
596
597 list.addAll(l);
598
599 } else if (Map.class.isInstance(value)) {
600
601 @SuppressWarnings("unchecked")
602 final Map<Object, Object> map = (Map<Object, Object>) value;
603
604 checkState(!map.containsKey(token0), "Duplicate key: " + token0);
605
606 map.put(toObject(token0), toObject(token2));
607
608 list.add(map);
609
610 } else {
611
612 throw new NotImplementedException("value.class: " + value.getClass().getName());
613 }
614
615 continue;
616 }
617
618 final Token token2 = yamlReader.readNextToken(false);
619
620 if (token2 != HYPHEN) {
621
622 throw new YamlReaderException(
623 yamlReader.getCurrentTokenLineNumber(),
624 yamlReader.getCurrentTokenColumnNumber(),
625 "Illegal token in array: " + token2);
626 }
627 }
628
629 return list;
630 }
631 }