WIP: variables-dialect
[maven-thymeleaf-skin] / src / main / java / de / juplo / thymeleaf / ImportVariablesAttrProcessor.java
index bc3f3da..63963fa 100644 (file)
@@ -2,16 +2,18 @@ package de.juplo.thymeleaf;
 
 
 import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonLocation;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.core.JsonToken;
-import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
 import java.io.InputStream;
 import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Locale;
-import java.util.Map.Entry;
-import java.util.logging.Level;
+import java.util.Map;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,14 +23,13 @@ import org.thymeleaf.Arguments;
 import org.thymeleaf.Configuration;
 import org.thymeleaf.TemplateProcessingParameters;
 import org.thymeleaf.context.IContext;
+import org.thymeleaf.context.VariablesMap;
 import org.thymeleaf.dom.Element;
 import org.thymeleaf.processor.ProcessorResult;
 import org.thymeleaf.processor.attr.AbstractAttrProcessor;
 import org.thymeleaf.resourceresolver.IResourceResolver;
-import org.thymeleaf.standard.expression.IStandardExpression;
 import org.thymeleaf.templateresolver.ITemplateResolver;
 import org.thymeleaf.templateresolver.TemplateResolution;
-import org.thymeleaf.util.StringUtils;
 
 
 
@@ -62,7 +63,7 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
 
   public ImportVariablesAttrProcessor()
   {
-    super("crumb");
+    super("variables");
   }
 
 
@@ -80,8 +81,25 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
         new TemplateProcessingParameters(
             config,
             templateName,
-            (IContext)null // << We will not execute the template, hence we need no context
-            );
+            new IContext() // << We will not execute the template, hence we need no context
+            {
+              @Override
+              public VariablesMap<String, Object> getVariables()
+              {
+                return new VariablesMap<>();
+              }
+
+              @Override
+              public Locale getLocale()
+              {
+                return Locale.getDefault();
+              }
+
+              @Override
+              public void addContextExecutionInfo(String templateName)
+              {
+              }
+            });
 
     for (ITemplateResolver t_resolver : config.getTemplateResolvers())
     {
@@ -91,7 +109,8 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
       if (!"JSON".equals(resolution.getTemplateMode()))
         continue;
       IResourceResolver r_resolver = resolution.getResourceResolver();
-      InputStream is = r_resolver.getResourceAsStream(params, templateName);
+      InputStream is =
+          r_resolver.getResourceAsStream(params, resolution.getResourceName());
       if (is == null)
         continue;
 
@@ -100,18 +119,15 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
         /** Read the JSON and create the variables */
         JsonParser parser = FACTORY.createParser(is);
 
-        JsonToken root = parser.nextToken();
-
+        JsonToken token = parser.nextToken();
 
-        JsonNode root = MAPPER.readTree(is);
-
-        if (root == null)
+        if (token == null)
         {
           LOG.warn("found empty content for {}", templateName);
           break;
         }
 
-        if (!root.isObject())
+        if (!JsonToken.START_OBJECT.equals(token))
         {
           LOG.error("{} must contain an object as root-element", templateName);
           throw new RuntimeException(
@@ -120,12 +136,40 @@ public class ImportVariablesAttrProcessor extends AbstractAttrProcessor
               " has to be an object, that contains the variable-definitions!"
               );
         }
-root.
-        for (Entry<String, JsonNode> entry : root.fields())
+
+        token = parser.nextToken();
+        if (token == null)
+          fail(parser, "unexpected EOF");
+        if (JsonToken.END_OBJECT.equals(token))
+        {
+          LOG.warn("found empty object for {}", templateName);
+          break;
+        }
+
+        do
         {
-          Object var = ImportVariablesAttrProcessor.convert(root.get(i));
-          element.setNodeLocalVariable(name, var);
+          if (!JsonToken.FIELD_NAME.equals(token))
+            fail(parser, "expected a field-name");
+
+          String var_name = parser.getText();
+          parser.nextToken();
+          Object var_value = convert(parser);
+
+          LOG.debug(
+              "defining variable {} of type {}",
+              var_name,
+              var_value == null ? "NULL" : var_value.getClass().getSimpleName()
+              );
+          element.setNodeLocalVariable(var_name, var_value);
+
+          token = parser.nextToken();
+          if (token == null)
+            fail(parser, "unexpected EOF");
         }
+        while (!JsonToken.END_OBJECT.equals(token));
+
+        if (parser.nextToken() != null)
+          fail(parser, "unexpected data after parsed variables");
       }
       catch (IOException e)
       {
@@ -147,8 +191,84 @@ root.
   }
 
 
-  public static Object convert(JsonNode node)
+  static Object convert(JsonParser parser) throws IOException
+  {
+    JsonToken token = parser.getCurrentToken();
+    if (token == null)
+      fail(parser, "unexpected EOF");
+
+    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 convertObject(parser);
+      case START_ARRAY:        return convertArray(parser);
+      case VALUE_TRUE:         return Boolean.TRUE;
+      case VALUE_FALSE:        return Boolean.FALSE;
+      case VALUE_NULL:         return null;
+    }
+
+    fail(parser, "unexpected token " + token.toString());
+    return null; // << Will never be reached, because fail always throws an exception
+  }
+
+  static Map<String, Object> convertObject(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 = convert(parser);
+      map.put(name, value);
+
+      token = parser.nextToken();
+      if (token == null)
+        fail(parser, "unexpected EOF");
+    }
+
+    return map;
+  }
+
+  static List<Object> convertArray(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(convert(parser));
+
+      token = parser.nextToken();
+      if (token == null)
+        fail(parser, "unexpected EOF");
+    }
+
+    return list;
+  }
+
+  static void fail(JsonParser parser, String message)
   {
-    return null;
+    JsonLocation location = parser.getCurrentLocation();
+    LOG.error(
+        "{} at char-offset {} (line {}, column {})",
+        message,
+        location.getCharOffset(),
+        location.getLineNr(),
+        location.getColumnNr()
+        );
+    throw new RuntimeException("Cannot parse JSON: " + message);
   }
 }