Refined packaging: moved all classes into package de.juplo.simplemapper
[simple-mapper] / src / main / java / de / juplo / simplemapper / SimpleMapper.java
diff --git a/src/main/java/de/juplo/simplemapper/SimpleMapper.java b/src/main/java/de/juplo/simplemapper/SimpleMapper.java
new file mode 100644 (file)
index 0000000..f9143d0
--- /dev/null
@@ -0,0 +1,401 @@
+package de.juplo.simplemapper;
+
+
+import com.fasterxml.jackson.core.JsonLocation;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Spliterator;
+import static java.util.Spliterator.IMMUTABLE;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+
+/**
+ *
+ * @author kai
+ */
+public abstract class SimpleMapper
+{
+  private static final Logger LOG =
+      LoggerFactory.getLogger(SimpleMapper.class);
+
+
+  public static Spliterator<Object> getArraySpliterator(final JsonParser parser)
+      throws
+        IOException
+  {
+    JsonToken token = parser.nextToken();
+
+    if (token == null)
+      return null;
+
+    if (!JsonToken.START_ARRAY.equals(token))
+      fail(parser, "The root-element must be an array!");
+
+    return new Spliterator<Object>()
+    {
+      @Override
+      public boolean tryAdvance(Consumer<? super Object> action)
+      {
+        try
+        {
+          JsonToken token = parser.nextToken();
+          if (token == null)
+            fail(parser, "Unexpected end of data!");
+          if (JsonToken.END_ARRAY.equals(token))
+          {
+            if (parser.nextToken() != null)
+              fail(parser, "unexpected data after parsed array");
+            return false;
+          }
+          action.accept(convertInternal(parser));
+          return true;
+        }
+        catch (IOException e)
+        {
+          throw new IllegalArgumentException(e);
+        }
+      }
+
+      @Override
+      public Spliterator<Object> trySplit()
+      {
+        return null;
+      }
+
+      @Override
+      public long estimateSize()
+      {
+        return Long.MAX_VALUE;
+      }
+
+      @Override
+      public int characteristics()
+      {
+        return IMMUTABLE;
+      }
+    };
+  }
+
+  public static Stream<Object> getArrayStream(final JsonParser parser)
+      throws
+        IOException
+  {
+    return StreamSupport.stream(getArraySpliterator(parser), false);
+  }
+
+  public static Iterator<Object> getArrayIterator(final JsonParser parser)
+      throws
+        IOException
+  {
+    Spliterator<Object> spliterator = getArraySpliterator(parser);
+    return new Iterator<Object>()
+    {
+      private Object next = null;
+
+
+      @Override
+      public boolean hasNext()
+      {
+        if (next != null)
+          return true;
+
+        return spliterator.tryAdvance((Object o) -> { next = o; });
+      }
+
+      @Override
+      public Object next()
+      {
+        if (next == null && !hasNext())
+          throw new NoSuchElementException();
+        Object o = next;
+        next = null;
+        return o;
+      }
+    };
+  }
+
+
+  public static Spliterator<Entry<String, Object>> getObjectSpliterator(final JsonParser parser)
+      throws
+        IOException
+  {
+    JsonToken token = parser.nextToken();
+
+    if (token == null)
+      return null;
+
+    if (!JsonToken.START_OBJECT.equals(token))
+      fail(parser, "The root-element must be an object!");
+
+    return new Spliterator<Entry<String, Object>>()
+    {
+      @Override
+      public boolean tryAdvance(Consumer<? super Entry<String, Object>> action)
+      {
+        try
+        {
+          JsonToken token = parser.nextToken();
+          if (token == null)
+            fail(parser, "Unexpected end of data!");
+          if (JsonToken.END_OBJECT.equals(token))
+          {
+            if (parser.nextToken() != null)
+              fail(parser, "unexpected data after parsed object");
+            return false;
+          }
+          if (!JsonToken.FIELD_NAME.equals(token))
+            fail(parser, "expected a field-name");
+          final String key = parser.getText();
+          parser.nextToken();
+          final Object value = convertInternal(parser);
+          action.accept(new Entry<String, Object>()
+          {
+            @Override
+            public String getKey()
+            {
+              return key;
+            }
+
+            @Override
+            public Object getValue()
+            {
+              return value;
+            }
+
+            @Override
+            public Object setValue(Object value)
+            {
+              throw new UnsupportedOperationException("Not supported.");
+            }
+          });
+          return true;
+        }
+        catch (IOException e)
+        {
+          throw new IllegalArgumentException(e);
+        }
+      }
+
+      @Override
+      public Spliterator<Entry<String, Object>> trySplit()
+      {
+        return null;
+      }
+
+      @Override
+      public long estimateSize()
+      {
+        return Long.MAX_VALUE;
+      }
+
+      @Override
+      public int characteristics()
+      {
+        return IMMUTABLE;
+      }
+    };
+  }
+
+  public static Stream<Entry<String, Object>> getObjectStream(final JsonParser parser)
+      throws
+        IOException
+  {
+    return StreamSupport.stream(getObjectSpliterator(parser), false);
+  }
+
+  public static Iterator<Entry<String, Object>> getObjectIterator(
+      final JsonParser parser
+      )
+      throws
+        IOException
+  {
+    Spliterator<Entry<String, Object>> spliterator = getObjectSpliterator(parser);
+    return new Iterator<Entry<String, Object>>()
+    {
+      private Entry<String, Object> next = null;
+
+
+      @Override
+      public boolean hasNext()
+      {
+        if (next != null)
+          return true;
+
+        return spliterator.tryAdvance((Entry<String, Object> e) -> { next = e; });
+      }
+
+      @Override
+      public Entry<String, Object> next()
+      {
+        if (next == null && !hasNext())
+          throw new NoSuchElementException();
+        Entry<String, Object> e = next;
+        next = null;
+        return e;
+      }
+    };
+  }
+
+
+  public static List<Object> convertArray(JsonParser parser) throws IOException
+  {
+    JsonToken token = parser.nextToken();
+
+    if (token == null)
+      return null;
+
+    if (!JsonToken.START_ARRAY.equals(token))
+      fail(parser, "The root-element must be an array!");
+
+    List<Object> array = convertArrayInternal(parser);
+
+    if (parser.nextToken() != null)
+      fail(parser, "unexpected data after parsed array");
+
+    return array;
+  }
+
+  public static Map<String, Object> convertObject(JsonParser parser) throws IOException
+  {
+    JsonToken token = parser.nextToken();
+
+    if (token == null)
+      return null;
+
+    if (!JsonToken.START_OBJECT.equals(token))
+      fail(parser, "The root-element must be an object!");
+
+    Map<String, Object> object = convertObjectInternal(parser);
+
+    if (parser.nextToken() != null)
+      fail(parser, "unexpected data after parsed object");
+
+    return object;
+  }
+
+  public static Object convert(JsonParser parser) throws IOException
+  {
+    JsonToken token = parser.nextToken();
+
+    if (token == null)
+      return null;
+
+    switch (token)
+    {
+      case START_ARRAY:
+      case START_OBJECT:
+        break;
+      default:
+        fail(parser, "The root-element must be either an object or an array!");
+    }
+
+    Object object = convertInternal(parser);
+
+    if (parser.nextToken() != null)
+      fail(parser, "unexpected data after parsed object");
+
+    return object;
+  }
+
+
+  static Object convertInternal(JsonParser parser) throws IOException
+  {
+    JsonToken token = parser.getCurrentToken();
+    if (token == null)
+    {
+      fail(parser, "unexpected EOF");
+      return null; // << Will never be reached, because fail always throws an exception
+    }
+
+    switch (token)
+    {
+      case VALUE_STRING:       return parser.getText();
+      case VALUE_NUMBER_INT:   return parser.getIntValue();
+      case VALUE_NUMBER_FLOAT: return parser.getDoubleValue();
+      case START_OBJECT:       return convertObjectInternal(parser);
+      case START_ARRAY:        return convertArrayInternal(parser);
+      case VALUE_TRUE:         return Boolean.TRUE;
+      case VALUE_FALSE:        return Boolean.FALSE;
+      case VALUE_NULL:         return null;
+    }
+
+    fail(parser, "unexpected token " + token);
+    return null; // << Will never be reached, because fail always throws an exception
+  }
+
+
+  static Map<String, Object> convertObjectInternal(JsonParser parser)
+      throws
+        IOException
+  {
+    JsonToken token = parser.nextToken();
+    if (token == null)
+      fail(parser, "unexpected EOF");
+
+    Map<String, Object> map = new LinkedHashMap<>();
+
+    while (!JsonToken.END_OBJECT.equals(token))
+    {
+      if (!JsonToken.FIELD_NAME.equals(token))
+        fail(parser, "expected a field-name");
+
+      String name = parser.getText();
+      parser.nextToken();
+      Object value = convertInternal(parser);
+      map.put(name, value);
+
+      token = parser.nextToken();
+      if (token == null)
+        fail(parser, "unexpected EOF");
+    }
+
+    return map;
+  }
+
+  static List<Object> convertArrayInternal(JsonParser parser) throws IOException
+  {
+    JsonToken token = parser.nextToken();
+    if (token == null)
+      fail(parser, "unexpected EOF");
+
+    List<Object> list = new LinkedList<>();
+
+    while (!JsonToken.END_ARRAY.equals(token))
+    {
+      list.add(convertInternal(parser));
+
+      token = parser.nextToken();
+      if (token == null)
+        fail(parser, "unexpected EOF");
+    }
+
+    return list;
+  }
+
+
+  static void fail(JsonParser parser, String message)
+  {
+    JsonLocation location = parser.getCurrentLocation();
+    LOG.error(
+        "{} at char-offset {} (line {}, column {})",
+        message,
+        location.getCharOffset(),
+        location.getLineNr(),
+        location.getColumnNr()
+        );
+    throw new IllegalArgumentException("Cannot parse JSON: " + message);
+  }
+}