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