View Javadoc
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  					// yamlReader.readNextToken();
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; // String
183 
184 		} else {
185 
186 			if ("true".contentEquals(token.s)) {
187 
188 				return Boolean.TRUE; // Boolean
189 
190 			} else if ("false".contentEquals(token.s)) {
191 
192 				return Boolean.FALSE; // Boolean
193 			}
194 			try {
195 
196 				return Integer.parseInt(token.s); // Integer
197 
198 			} catch (final NumberFormatException parseIntE) {
199 
200 				try {
201 
202 					return Long.parseLong(token.s); // Long
203 
204 				} catch (final NumberFormatException parseLongE) {
205 
206 					return token.s; // String
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 			// if (token0 == CLOSE_BRACKET) {
358 			//
359 			// return map;
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 				// return map;
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 				// final Object value = toObject(token2);
498 
499 				// map.put(key, value);
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 					// TODO this works only for one-item lists
581 
582 					continue;
583 					// throw new NotImplementedException("");
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 }