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.io.InputStream;
10 import java.util.Collections;
11 import java.util.Iterator;
12 import java.util.LinkedHashMap;
13 import java.util.LinkedList;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.NoSuchElementException;
17 import java.util.Spliterator;
18 import static java.util.Spliterator.IMMUTABLE;
19 import java.util.Spliterators;
20 import java.util.function.Consumer;
21 import java.util.stream.Stream;
22 import java.util.stream.StreamSupport;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26
27
28 /**
29  *
30  * @author kai
31  */
32 public class SimpleMapper
33 {
34   private static final Logger LOG =
35       LoggerFactory.getLogger(SimpleMapper.class);
36
37
38   private JsonFactory factory = new JsonFactory();
39
40
41   public static Stream<Object> getArrayStream(final JsonParser parser)
42       throws
43         IOException
44   {
45     return StreamSupport.stream(getArraySpliterator(parser), false);
46   }
47
48   public static Spliterator<Object> getArraySpliterator(final JsonParser parser)
49       throws
50         IOException
51   {
52     JsonToken token = parser.nextToken();
53
54     if (token == null)
55     {
56       LOG.warn("empty input");
57       return Spliterators.emptySpliterator();
58     }
59
60     if (!JsonToken.START_ARRAY.equals(token))
61     {
62       throw new IllegalArgumentException("The root-element must be an array!");
63     }
64
65     return new Spliterator<Object>()
66     {
67       @Override
68       public boolean tryAdvance(Consumer<? super Object> action)
69       {
70         try
71         {
72           JsonToken token = parser.nextToken();
73           if (token == null)
74             throw new IllegalArgumentException("Unexpected end of data!");
75           if (JsonToken.END_ARRAY.equals(token))
76             return false;
77           action.accept(convert(parser));
78           return true;
79         }
80         catch (IOException e)
81         {
82           throw new IllegalArgumentException(e);
83         }
84       }
85
86       @Override
87       public Spliterator<Object> trySplit()
88       {
89         return null;
90       }
91
92       @Override
93       public long estimateSize()
94       {
95         return Long.MAX_VALUE;
96       }
97
98       @Override
99       public int characteristics()
100       {
101         return IMMUTABLE;
102       }
103     };
104   }
105
106   public static Iterator<Object> getArrayIterator(final JsonParser parser)
107       throws
108         IOException
109   {
110     JsonToken token = parser.nextToken();
111
112     if (token == null)
113     {
114       LOG.warn("empty input");
115       return Collections.emptyIterator();
116     }
117
118     if (!JsonToken.START_ARRAY.equals(token))
119     {
120       throw new IllegalArgumentException("The root-element must be an array!");
121     }
122
123     return new Iterator<Object>()
124     {
125       
126       @Override
127       public boolean tryAdvance(Consumer<? super Object> action)
128       {
129         try
130         {
131           JsonToken token = parser.nextToken();
132           if (token == null)
133             throw new IllegalArgumentException("Unexpected end of data!");
134           if (JsonToken.END_ARRAY.equals(token))
135             throw new NoSuchElementException();
136           action.accept(convert(parser));
137           return true;
138         }
139         catch (IOException e)
140         {
141           throw new IllegalArgumentException(e);
142         }
143       }
144       @Override
145       public boolean hasNext()
146       {
147       }
148
149       @Override
150       public Object next()
151       {
152         try
153         {
154           JsonToken token = parser.nextToken();
155           if (token == null)
156             throw new IllegalArgumentException("Unexpected end of data!");
157           if (JsonToken.END_ARRAY.equals(token))
158             return false;
159           action.accept(convert(parser));
160           return true;
161         }
162         catch (IOException e)
163         {
164           throw new IllegalArgumentException(e);
165         }
166       }
167     };
168   }
169
170   public Object processAttribute(InputStream is)
171   {
172     try
173     {
174       /**
175        * Read the JSON and create the variables
176        */
177       JsonParser parser = factory.createParser(is);
178
179       JsonToken token = parser.nextToken();
180
181       if (token == null)
182       {
183         LOG.warn("empty input-stream");
184         return null;
185       }
186
187       Object result = convert(parser);
188
189       if (parser.nextToken() != null)
190         fail(parser, "unexpected data after parsed variables");
191
192       return result;
193     }
194     catch (IOException e)
195     {
196       LOG.error("cannot parse input-stream as JSON: {}", e.getMessage());
197       throw new RuntimeException(e);
198     }
199   }
200
201
202   static Object convert(JsonParser parser) throws IOException
203   {
204     JsonToken token = parser.getCurrentToken();
205     if (token == null)
206       fail(parser, "unexpected EOF");
207
208     switch (token)
209     {
210       case VALUE_STRING:       return parser.getText();
211       case VALUE_NUMBER_INT:   return parser.getIntValue();
212       case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
213       case START_OBJECT:       return convertObject(parser);
214       case START_ARRAY:        return convertArray(parser);
215       case VALUE_TRUE:         return Boolean.TRUE;
216       case VALUE_FALSE:        return Boolean.FALSE;
217       case VALUE_NULL:         return null;
218     }
219
220     fail(parser, "unexpected token " + token.toString());
221     return null; // << Will never be reached, because fail always throws an exception
222   }
223
224   static Map<String, Object> convertObject(JsonParser parser) throws IOException
225   {
226     JsonToken token = parser.nextToken();
227     if (token == null)
228       fail(parser, "unexpected EOF");
229
230     Map<String, Object> map = new LinkedHashMap<>();
231
232     while (!JsonToken.END_OBJECT.equals(token))
233     {
234       if (!JsonToken.FIELD_NAME.equals(token))
235         fail(parser, "expected a field-name");
236
237       String name = parser.getText();
238       parser.nextToken();
239       Object value = convert(parser);
240       map.put(name, value);
241
242       token = parser.nextToken();
243       if (token == null)
244         fail(parser, "unexpected EOF");
245     }
246
247     return map;
248   }
249
250   static List<Object> convertArray(JsonParser parser) throws IOException
251   {
252     JsonToken token = parser.nextToken();
253     if (token == null)
254       fail(parser, "unexpected EOF");
255
256     List<Object> list = new LinkedList<>();
257
258     while (!JsonToken.END_ARRAY.equals(token))
259     {
260       list.add(convert(parser));
261
262       token = parser.nextToken();
263       if (token == null)
264         fail(parser, "unexpected EOF");
265     }
266
267     return list;
268   }
269
270   static void fail(JsonParser parser, String message)
271   {
272     JsonLocation location = parser.getCurrentLocation();
273     LOG.error(
274         "{} at char-offset {} (line {}, column {})",
275         message,
276         location.getCharOffset(),
277         location.getLineNr(),
278         location.getColumnNr()
279         );
280     throw new RuntimeException("Cannot parse JSON: " + message);
281   }
282 }