SLING-9829 - data-sly-element should correctly handle void elements

* defined the set of void elements in ElementPlugin; every time the
data-sly-element plugin is used, the dynamically passed tag name will
be checked against the set; if the tag is a void element, no closing tag
will be added
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/StringConstant.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/StringConstant.java
index 2862490..4a6501f 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/StringConstant.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/StringConstant.java
@@ -18,6 +18,8 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.compiler.expression.nodes;
 
+import java.util.Objects;
+
 import org.apache.sling.scripting.sightly.compiler.expression.NodeVisitor;
 
 /**
@@ -63,4 +65,20 @@
                 '}';
     }
 
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(text);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof StringConstant) {
+            StringConstant other = (StringConstant) obj;
+            return Objects.equals(text, other.text);
+        }
+        return false;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/package-info.java
index e2c21a3..efda58f 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/package-info.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/compiler/expression/nodes/package-info.java
@@ -19,7 +19,7 @@
  * The {@code org.apache.sling.scripting.sightly.compiler.expression.nodes} package exposes the various
  * {@link org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode} types.
  */
-@Version("1.2.0")
+@Version("1.2.1")
 package org.apache.sling.scripting.sightly.compiler.expression.nodes;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java
index 6096c20..9df6c3b 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java
@@ -45,6 +45,10 @@
         return symbolGenerator.next(hint);
     }
 
+    public String generateGlobalVariable(String hint) {
+        return symbolGenerator.global(hint);
+    }
+
     public Expression adjustToContext(Expression expression, MarkupContext context, ExpressionContext expressionContext) {
         return expressionWrapper.adjustToContext(expression, context, expressionContext);
     }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/util/SymbolGenerator.java b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/util/SymbolGenerator.java
index 6277b1f..39b11bf 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/util/SymbolGenerator.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/util/SymbolGenerator.java
@@ -18,12 +18,16 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.compiler.util;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public class SymbolGenerator {
 
     public static final String DEFAULT_VAR_PREFIX = "var_";
 
     private int counter = 0;
     private final String prefix;
+    private final Map<String, String> globals = new HashMap<>();
 
     public SymbolGenerator() {
         this(DEFAULT_VAR_PREFIX);
@@ -34,10 +38,15 @@
     }
 
     public String next(String hint) {
-        String middle = (hint != null) ? hint.replaceAll("\\-", "_") : "";
+        String middle = (hint != null) ? hint.replace("-", "_") : "";
         return prefix + middle + counter++;
     }
 
+    public String global(String hint) {
+        String name = prefix +  ((hint != null) ? hint.replace("-", "_") : "");
+        return globals.computeIfAbsent(name, key -> name + counter++);
+    }
+
     public String next() {
         return next(null);
     }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java b/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java
index 29439b1..9a58ef2 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java
@@ -18,6 +18,12 @@
  ******************************************************************************/
 package org.apache.sling.scripting.sightly.impl.plugin;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
 import org.apache.sling.scripting.sightly.compiler.commands.Conditional;
 import org.apache.sling.scripting.sightly.compiler.commands.OutText;
 import org.apache.sling.scripting.sightly.compiler.commands.OutputVariable;
@@ -25,7 +31,14 @@
 import org.apache.sling.scripting.sightly.compiler.expression.Expression;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
 import org.apache.sling.scripting.sightly.compiler.expression.MarkupContext;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.ArrayLiteral;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperation;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.BinaryOperator;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.Identifier;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.RuntimeCall;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.StringConstant;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.UnaryOperation;
+import org.apache.sling.scripting.sightly.compiler.expression.nodes.UnaryOperator;
 import org.apache.sling.scripting.sightly.impl.compiler.PushStream;
 import org.apache.sling.scripting.sightly.impl.compiler.frontend.CompilerContext;
 import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext;
@@ -36,27 +49,40 @@
         name = "element";
     }
 
+    public static final Set<ExpressionNode> VOID_ELEMENTS =
+            Collections.unmodifiableSet(new HashSet<>(
+                    Arrays.asList(new StringConstant("area"), new StringConstant("base"), new StringConstant("br"),
+                            new StringConstant("col"), new StringConstant("embed"), new StringConstant("hr"), new StringConstant("img"),
+                            new StringConstant("input"), new StringConstant("link"), new StringConstant("meta"),
+                            new StringConstant("param"), new StringConstant("source"), new StringConstant("track"),
+                            new StringConstant("wbr"))));
+
     @Override
     public PluginInvoke invoke(final Expression expression, final PluginCallInfo callInfo, final CompilerContext compilerContext) {
 
         return new DefaultPluginInvoke() {
 
-            private final ExpressionNode node = adjustContext(compilerContext, expression, MarkupContext.ELEMENT_NAME, ExpressionContext
-                    .ELEMENT).getRoot();
-            private String tagVar = compilerContext.generateVariable("tagVar");
+            private final ExpressionNode node = adjustContext(compilerContext, expression).getRoot();
+            private final String tagVar = compilerContext.generateVariable("tagVar");
+            private final String tagAllowed = compilerContext.generateVariable("tagAllowed");
+            private final String voidElements = compilerContext.generateGlobalVariable("elementPluginVoidElements");
+            private final String selfClosingTag = compilerContext.generateVariable("selfClosingTag");
 
             @Override
             public void beforeElement(PushStream stream, String tagName) {
+                stream.write(new VariableBinding.Global(voidElements, new ArrayLiteral(new ArrayList<>(VOID_ELEMENTS))));
                 stream.write(new VariableBinding.Start(tagVar, node));
+                stream.write(new VariableBinding.Start(tagAllowed, new UnaryOperation(UnaryOperator.NOT,
+                        new UnaryOperation(UnaryOperator.NOT, new Identifier(tagVar)))));
             }
 
             @Override
             public void beforeTagOpen(PushStream stream) {
-                stream.write(new Conditional.Start(tagVar, true));
+                stream.write(new Conditional.Start(tagAllowed, true));
                 stream.write(new OutText("<"));
                 stream.write(new OutputVariable(tagVar));
                 stream.write(Conditional.END);
-                stream.write(new Conditional.Start(tagVar, false));
+                stream.write(new Conditional.Start(tagAllowed, false));
             }
 
             @Override
@@ -65,15 +91,34 @@
             }
 
             @Override
+            public void afterAttributes(PushStream stream) {
+                stream.write(new Conditional.Start(tagAllowed, true));
+                stream.write(new OutText(">"));
+                stream.write(Conditional.END);
+                stream.write(new Conditional.Start(tagAllowed, false));
+            }
+
+            @Override
+            public void afterTagOpen(PushStream stream) {
+                stream.write(Conditional.END);
+            }
+
+            @Override
             public void beforeTagClose(PushStream stream, boolean isSelfClosing) {
-                if (!isSelfClosing) {
-                    stream.write(new Conditional.Start(tagVar, true));
-                    stream.write(new OutText("</"));
-                    stream.write(new OutputVariable(tagVar));
-                    stream.write(new OutText(">"));
-                    stream.write(Conditional.END);
-                }
-                stream.write(new Conditional.Start(tagVar, false));
+                stream.write(new Conditional.Start(tagAllowed, true));
+                stream.write(
+                        new VariableBinding.Start(selfClosingTag,
+                                new BinaryOperation(BinaryOperator.IN, new Identifier(tagVar), new Identifier(voidElements))
+                        )
+                );
+                stream.write(new Conditional.Start(selfClosingTag, false));
+                stream.write(new OutText("</"));
+                stream.write(new OutputVariable(tagVar));
+                stream.write(new OutText(">"));
+                stream.write(Conditional.END);
+                stream.write(VariableBinding.END);
+                stream.write(Conditional.END);
+                stream.write(new Conditional.Start(tagAllowed, false));
             }
 
             @Override
@@ -84,13 +129,13 @@
             @Override
             public void afterElement(PushStream stream) {
                 stream.write(VariableBinding.END);
+                stream.write(VariableBinding.END);
             }
         };
 
     }
 
-    private Expression adjustContext(CompilerContext compilerContext, Expression expression, MarkupContext markupContext,
-                                     ExpressionContext expressionContext) {
+    private Expression adjustContext(CompilerContext compilerContext, Expression expression) {
         ExpressionNode root = expression.getRoot();
         if (root instanceof RuntimeCall) {
             RuntimeCall runtimeCall = (RuntimeCall) root;
@@ -98,6 +143,6 @@
                 return expression;
             }
         }
-        return compilerContext.adjustToContext(expression, markupContext, expressionContext);
+        return compilerContext.adjustToContext(expression, MarkupContext.ELEMENT_NAME, ExpressionContext.ELEMENT);
     }
 }