f15bf25ff81ebb1337f34ada47e3abcd95a97c79
[maven-thymeleaf-skin] / src / main / java / de / juplo / jackson / SimpleMapper.java
1 package de.juplo.jackson;
2
3
4 import com.fasterxml.jackson.core.JsonLocation;
5 import com.fasterxml.jackson.core.JsonParser;
6 import com.fasterxml.jackson.core.JsonToken;
7 import java.io.IOException;
8 import java.util.Collections;
9 import java.util.Iterator;
10 import java.util.LinkedHashMap;
11 import java.util.LinkedList;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Map.Entry;
15 import java.util.NoSuchElementException;
16 import java.util.Spliterator;
17 import static java.util.Spliterator.IMMUTABLE;
18 import java.util.Spliterators;
19 import java.util.function.Consumer;
20 import java.util.stream.Stream;
21 import java.util.stream.StreamSupport;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25
26
27 /**
28  *
29  * @author kai
30  */
31 public abstract class SimpleMapper
32 {
33   private static final Logger LOG =
34       LoggerFactory.getLogger(SimpleMapper.class);
35
36
37   public static Spliterator<Object> getArraySpliterator(final JsonParser parser)
38       throws
39         IOException
40   {
41     JsonToken token = parser.nextToken();
42
43     if (token == null)
44     {
45       LOG.warn("empty input");
46       return Spliterators.emptySpliterator();
47     }
48
49     if (!JsonToken.START_ARRAY.equals(token))
50       fail(parser, "The root-element must be an array!");
51
52     return new Spliterator<Object>()
53     {
54       @Override
55       public boolean tryAdvance(Consumer<? super Object> action)
56       {
57         try
58         {
59           JsonToken token = parser.nextToken();
60           if (token == null)
61             fail(parser, "Unexpected end of data!");
62           if (JsonToken.END_ARRAY.equals(token))
63           {
64             if (parser.nextToken() != null)
65               fail(parser, "unexpected data after parsed array");
66             return false;
67           }
68           action.accept(convertInternal(parser));
69           return true;
70         }
71         catch (IOException e)
72         {
73           throw new IllegalArgumentException(e);
74         }
75       }
76
77       @Override
78       public Spliterator<Object> trySplit()
79       {
80         return null;
81       }
82
83       @Override
84       public long estimateSize()
85       {
86         return Long.MAX_VALUE;
87       }
88
89       @Override
90       public int characteristics()
91       {
92         return IMMUTABLE;
93       }
94     };
95   }
96
97   public static Stream<Object> getArrayStream(final JsonParser parser)
98       throws
99         IOException
100   {
101     return StreamSupport.stream(getArraySpliterator(parser), false);
102   }
103
104   public static Iterator<Object> getArrayIterator(final JsonParser parser)
105       throws
106         IOException
107   {
108     Spliterator<Object> spliterator = getArraySpliterator(parser);
109     return new Iterator<Object>()
110     {
111       private Object next = null;
112
113
114       @Override
115       public boolean hasNext()
116       {
117         if (next != null)
118           return true;
119
120         return spliterator.tryAdvance(new Consumer<Object>()
121         {
122           @Override
123           public void accept(Object o)
124           {
125             next = o;
126           }
127         });
128       }
129
130       @Override
131       public Object next()
132       {
133         if (next == null && !hasNext())
134           throw new NoSuchElementException();
135         Object o = next;
136         next = null;
137         return o;
138       }
139     };
140   }
141
142
143   public static Spliterator<Entry<String, Object>> getObjectSpliterator(final JsonParser parser)
144       throws
145         IOException
146   {
147     JsonToken token = parser.nextToken();
148
149     if (token == null)
150     {
151       LOG.warn("empty input");
152       return Spliterators.emptySpliterator();
153     }
154
155     if (!JsonToken.START_OBJECT.equals(token))
156       fail(parser, "The root-element must be an object!");
157
158     return new Spliterator<Entry<String, Object>>()
159     {
160       @Override
161       public boolean tryAdvance(Consumer<? super Entry<String, Object>> action)
162       {
163         try
164         {
165           JsonToken token = parser.nextToken();
166           if (token == null)
167             fail(parser, "Unexpected end of data!");
168           if (JsonToken.END_OBJECT.equals(token))
169           {
170             if (parser.nextToken() != null)
171               fail(parser, "unexpected data after parsed object");
172             return false;
173           }
174           if (!JsonToken.FIELD_NAME.equals(token))
175             fail(parser, "expected a field-name");
176           final String key = parser.getText();
177           parser.nextToken();
178           final Object value = convertInternal(parser);
179           action.accept(new Entry<String, Object>()
180           {
181             @Override
182             public String getKey()
183             {
184               return key;
185             }
186
187             @Override
188             public Object getValue()
189             {
190               return value;
191             }
192
193             @Override
194             public Object setValue(Object value)
195             {
196               throw new UnsupportedOperationException("Not supported.");
197             }
198           });
199           return true;
200         }
201         catch (IOException e)
202         {
203           throw new IllegalArgumentException(e);
204         }
205       }
206
207       @Override
208       public Spliterator<Entry<String, Object>> trySplit()
209       {
210         return null;
211       }
212
213       @Override
214       public long estimateSize()
215       {
216         return Long.MAX_VALUE;
217       }
218
219       @Override
220       public int characteristics()
221       {
222         return IMMUTABLE;
223       }
224     };
225   }
226
227   public static Stream<Entry<String, Object>> getObjectStream(final JsonParser parser)
228       throws
229         IOException
230   {
231     return StreamSupport.stream(getObjectSpliterator(parser), false);
232   }
233
234   public static Iterator<Entry<String, Object>> getObjectIterator(
235       final JsonParser parser
236       )
237       throws
238         IOException
239   {
240     Spliterator<Entry<String, Object>> spliterator = getObjectSpliterator(parser);
241     return new Iterator<Entry<String, Object>>()
242     {
243       private Entry<String, Object> next = null;
244
245
246       @Override
247       public boolean hasNext()
248       {
249         if (next != null)
250           return true;
251
252         return spliterator.tryAdvance(new Consumer<Entry<String, Object>>()
253         {
254           @Override
255           public void accept(Entry<String, Object> e)
256           {
257             next = e;
258           }
259         });
260       }
261
262       @Override
263       public Entry<String, Object> next()
264       {
265         if (next == null && !hasNext())
266           throw new NoSuchElementException();
267         Entry<String, Object> e = next;
268         next = null;
269         return e;
270       }
271     };
272   }
273
274
275   public static List<Object> convertArray(JsonParser parser) throws IOException
276   {
277     JsonToken token = parser.nextToken();
278
279     if (token == null)
280     {
281       LOG.warn("empty input");
282       return Collections.EMPTY_LIST;
283     }
284
285     if (!JsonToken.START_ARRAY.equals(token))
286       fail(parser, "The root-element must be an array!");
287
288     List<Object> array = convertArrayInternal(parser);
289
290     if (parser.nextToken() != null)
291       fail(parser, "unexpected data after parsed array");
292
293     return array;
294   }
295
296   public static Map<String, Object> convertObject(JsonParser parser) throws IOException
297   {
298     JsonToken token = parser.nextToken();
299
300     if (token == null)
301     {
302       LOG.warn("empty input");
303       return Collections.EMPTY_MAP;
304     }
305
306     if (!JsonToken.START_OBJECT.equals(token))
307       fail(parser, "The root-element must be an object!");
308
309     Map<String, Object> object = convertObjectInternal(parser);
310
311     if (parser.nextToken() != null)
312       fail(parser, "unexpected data after parsed object");
313
314     return object;
315   }
316
317   public static Object convert(JsonParser parser) throws IOException
318   {
319     JsonToken token = parser.nextToken();
320
321     if (token == null)
322     {
323       LOG.warn("empty input");
324       return null;
325     }
326
327     switch (token)
328     {
329       case START_ARRAY:
330       case START_OBJECT:
331         break;
332       default:
333         fail(parser, "The root-element must be either an object or an array!");
334     }
335
336     Object object = convertInternal(parser);
337
338     if (parser.nextToken() != null)
339       fail(parser, "unexpected data after parsed object");
340
341     return object;
342   }
343
344
345   static Object convertInternal(JsonParser parser) throws IOException
346   {
347     JsonToken token = parser.getCurrentToken();
348     if (token == null)
349       fail(parser, "unexpected EOF");
350
351     switch (token)
352     {
353       case VALUE_STRING:       return parser.getText();
354       case VALUE_NUMBER_INT:   return parser.getIntValue();
355       case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
356       case START_OBJECT:       return convertObjectInternal(parser);
357       case START_ARRAY:        return convertArrayInternal(parser);
358       case VALUE_TRUE:         return Boolean.TRUE;
359       case VALUE_FALSE:        return Boolean.FALSE;
360       case VALUE_NULL:         return null;
361     }
362
363     fail(parser, "unexpected token " + token.toString());
364     return null; // << Will never be reached, because fail always throws an exception
365   }
366
367
368   static Map<String, Object> convertObjectInternal(JsonParser parser)
369       throws
370         IOException
371   {
372     JsonToken token = parser.nextToken();
373     if (token == null)
374       fail(parser, "unexpected EOF");
375
376     Map<String, Object> map = new LinkedHashMap<>();
377
378     while (!JsonToken.END_OBJECT.equals(token))
379     {
380       if (!JsonToken.FIELD_NAME.equals(token))
381         fail(parser, "expected a field-name");
382
383       String name = parser.getText();
384       parser.nextToken();
385       Object value = convertInternal(parser);
386       map.put(name, value);
387
388       token = parser.nextToken();
389       if (token == null)
390         fail(parser, "unexpected EOF");
391     }
392
393     return map;
394   }
395
396   static List<Object> convertArrayInternal(JsonParser parser) throws IOException
397   {
398     JsonToken token = parser.nextToken();
399     if (token == null)
400       fail(parser, "unexpected EOF");
401
402     List<Object> list = new LinkedList<>();
403
404     while (!JsonToken.END_ARRAY.equals(token))
405     {
406       list.add(convertInternal(parser));
407
408       token = parser.nextToken();
409       if (token == null)
410         fail(parser, "unexpected EOF");
411     }
412
413     return list;
414   }
415
416
417   static void fail(JsonParser parser, String message)
418   {
419     JsonLocation location = parser.getCurrentLocation();
420     LOG.error(
421         "{} at char-offset {} (line {}, column {})",
422         message,
423         location.getCharOffset(),
424         location.getLineNr(),
425         location.getColumnNr()
426         );
427     throw new IllegalArgumentException("Cannot parse JSON: " + message);
428   }
429 }