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