Shortend some annonymous-class-expressions by using lambda-expressions
[simple-mapper] / 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((Object o) -> { next = o; });
121       }
122
123       @Override
124       public Object next()
125       {
126         if (next == null && !hasNext())
127           throw new NoSuchElementException();
128         Object o = next;
129         next = null;
130         return o;
131       }
132     };
133   }
134
135
136   public static Spliterator<Entry<String, Object>> getObjectSpliterator(final JsonParser parser)
137       throws
138         IOException
139   {
140     JsonToken token = parser.nextToken();
141
142     if (token == null)
143     {
144       LOG.warn("empty input");
145       return Spliterators.emptySpliterator();
146     }
147
148     if (!JsonToken.START_OBJECT.equals(token))
149       fail(parser, "The root-element must be an object!");
150
151     return new Spliterator<Entry<String, Object>>()
152     {
153       @Override
154       public boolean tryAdvance(Consumer<? super Entry<String, Object>> action)
155       {
156         try
157         {
158           JsonToken token = parser.nextToken();
159           if (token == null)
160             fail(parser, "Unexpected end of data!");
161           if (JsonToken.END_OBJECT.equals(token))
162           {
163             if (parser.nextToken() != null)
164               fail(parser, "unexpected data after parsed object");
165             return false;
166           }
167           if (!JsonToken.FIELD_NAME.equals(token))
168             fail(parser, "expected a field-name");
169           final String key = parser.getText();
170           parser.nextToken();
171           final Object value = convertInternal(parser);
172           action.accept(new Entry<String, Object>()
173           {
174             @Override
175             public String getKey()
176             {
177               return key;
178             }
179
180             @Override
181             public Object getValue()
182             {
183               return value;
184             }
185
186             @Override
187             public Object setValue(Object value)
188             {
189               throw new UnsupportedOperationException("Not supported.");
190             }
191           });
192           return true;
193         }
194         catch (IOException e)
195         {
196           throw new IllegalArgumentException(e);
197         }
198       }
199
200       @Override
201       public Spliterator<Entry<String, Object>> trySplit()
202       {
203         return null;
204       }
205
206       @Override
207       public long estimateSize()
208       {
209         return Long.MAX_VALUE;
210       }
211
212       @Override
213       public int characteristics()
214       {
215         return IMMUTABLE;
216       }
217     };
218   }
219
220   public static Stream<Entry<String, Object>> getObjectStream(final JsonParser parser)
221       throws
222         IOException
223   {
224     return StreamSupport.stream(getObjectSpliterator(parser), false);
225   }
226
227   public static Iterator<Entry<String, Object>> getObjectIterator(
228       final JsonParser parser
229       )
230       throws
231         IOException
232   {
233     Spliterator<Entry<String, Object>> spliterator = getObjectSpliterator(parser);
234     return new Iterator<Entry<String, Object>>()
235     {
236       private Entry<String, Object> next = null;
237
238
239       @Override
240       public boolean hasNext()
241       {
242         if (next != null)
243           return true;
244
245         return spliterator.tryAdvance((Entry<String, Object> e) -> { next = e; });
246       }
247
248       @Override
249       public Entry<String, Object> next()
250       {
251         if (next == null && !hasNext())
252           throw new NoSuchElementException();
253         Entry<String, Object> e = next;
254         next = null;
255         return e;
256       }
257     };
258   }
259
260
261   public static List<Object> convertArray(JsonParser parser) throws IOException
262   {
263     JsonToken token = parser.nextToken();
264
265     if (token == null)
266     {
267       LOG.warn("empty input");
268       return Collections.EMPTY_LIST;
269     }
270
271     if (!JsonToken.START_ARRAY.equals(token))
272       fail(parser, "The root-element must be an array!");
273
274     List<Object> array = convertArrayInternal(parser);
275
276     if (parser.nextToken() != null)
277       fail(parser, "unexpected data after parsed array");
278
279     return array;
280   }
281
282   public static Map<String, Object> convertObject(JsonParser parser) throws IOException
283   {
284     JsonToken token = parser.nextToken();
285
286     if (token == null)
287     {
288       LOG.warn("empty input");
289       return Collections.EMPTY_MAP;
290     }
291
292     if (!JsonToken.START_OBJECT.equals(token))
293       fail(parser, "The root-element must be an object!");
294
295     Map<String, Object> object = convertObjectInternal(parser);
296
297     if (parser.nextToken() != null)
298       fail(parser, "unexpected data after parsed object");
299
300     return object;
301   }
302
303   public static Object convert(JsonParser parser) throws IOException
304   {
305     JsonToken token = parser.nextToken();
306
307     if (token == null)
308     {
309       LOG.warn("empty input");
310       return null;
311     }
312
313     switch (token)
314     {
315       case START_ARRAY:
316       case START_OBJECT:
317         break;
318       default:
319         fail(parser, "The root-element must be either an object or an array!");
320     }
321
322     Object object = convertInternal(parser);
323
324     if (parser.nextToken() != null)
325       fail(parser, "unexpected data after parsed object");
326
327     return object;
328   }
329
330
331   static Object convertInternal(JsonParser parser) throws IOException
332   {
333     JsonToken token = parser.getCurrentToken();
334     if (token == null)
335       fail(parser, "unexpected EOF");
336
337     switch (token)
338     {
339       case VALUE_STRING:       return parser.getText();
340       case VALUE_NUMBER_INT:   return parser.getIntValue();
341       case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
342       case START_OBJECT:       return convertObjectInternal(parser);
343       case START_ARRAY:        return convertArrayInternal(parser);
344       case VALUE_TRUE:         return Boolean.TRUE;
345       case VALUE_FALSE:        return Boolean.FALSE;
346       case VALUE_NULL:         return null;
347     }
348
349     fail(parser, "unexpected token " + token.toString());
350     return null; // << Will never be reached, because fail always throws an exception
351   }
352
353
354   static Map<String, Object> convertObjectInternal(JsonParser parser)
355       throws
356         IOException
357   {
358     JsonToken token = parser.nextToken();
359     if (token == null)
360       fail(parser, "unexpected EOF");
361
362     Map<String, Object> map = new LinkedHashMap<>();
363
364     while (!JsonToken.END_OBJECT.equals(token))
365     {
366       if (!JsonToken.FIELD_NAME.equals(token))
367         fail(parser, "expected a field-name");
368
369       String name = parser.getText();
370       parser.nextToken();
371       Object value = convertInternal(parser);
372       map.put(name, value);
373
374       token = parser.nextToken();
375       if (token == null)
376         fail(parser, "unexpected EOF");
377     }
378
379     return map;
380   }
381
382   static List<Object> convertArrayInternal(JsonParser parser) throws IOException
383   {
384     JsonToken token = parser.nextToken();
385     if (token == null)
386       fail(parser, "unexpected EOF");
387
388     List<Object> list = new LinkedList<>();
389
390     while (!JsonToken.END_ARRAY.equals(token))
391     {
392       list.add(convertInternal(parser));
393
394       token = parser.nextToken();
395       if (token == null)
396         fail(parser, "unexpected EOF");
397     }
398
399     return list;
400   }
401
402
403   static void fail(JsonParser parser, String message)
404   {
405     JsonLocation location = parser.getCurrentLocation();
406     LOG.error(
407         "{} at char-offset {} (line {}, column {})",
408         message,
409         location.getCharOffset(),
410         location.getLineNr(),
411         location.getColumnNr()
412         );
413     throw new IllegalArgumentException("Cannot parse JSON: " + message);
414   }
415 }