SLING-7205 - Generate import statements for data-sly-use Java use objects

* added support for generating the import statements for fully qualified Java
class names passed as identifiers to data-sly-resource; use objects stored in the
repository are ignored

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1812544 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index b891e9d..51b6c0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
 
     <properties>
         <jacoco.maven.plugin.version>0.7.9</jacoco.maven.plugin.version>
+        <sling.java.version>8</sling.java.version>
     </properties>
 
     <!-- ======================================================================= -->
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaClassBackendCompiler.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaClassBackendCompiler.java
index 87e9655..8069cb0 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaClassBackendCompiler.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaClassBackendCompiler.java
@@ -38,7 +38,8 @@
     private static final String mainTemplate;
     private static final String childTemplate;
 
-    private UnitBuilder unitBuilder = new UnitBuilder();
+    private UnitBuilder unitBuilder;
+    private final JavaImportsAnalyzer JAVA_IMPORTS_ANALYZER;
 
     static {
         try {
@@ -50,6 +51,14 @@
         }
     }
 
+    public JavaClassBackendCompiler() {
+        this(new JavaImportsAnalyzer() {});
+    }
+
+    public JavaClassBackendCompiler(JavaImportsAnalyzer javaImportsAnalyzer) {
+        JAVA_IMPORTS_ANALYZER = javaImportsAnalyzer;
+        unitBuilder = new UnitBuilder(JAVA_IMPORTS_ANALYZER);
+    }
 
     @Override
     public void handle(CommandStream stream) {
@@ -76,6 +85,13 @@
         JavaClassTemplate mainTemplate = newMainTemplate();
         mainTemplate.setPackageName(classInfo.getPackageName());
         mainTemplate.setClassName(classInfo.getSimpleClassName());
+        StringBuilder imports = new StringBuilder();
+        for (String importClass : unitBuilder.getImports()) {
+            if (JAVA_IMPORTS_ANALYZER.allowImport(importClass)) {
+                imports.append("import ").append(importClass).append(";").append(System.lineSeparator());
+            }
+        }
+        mainTemplate.setImports(imports.toString());
         processCompilationResult(compilationOutput, mainTemplate);
         return mainTemplate.toString();
     }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaImportsAnalyzer.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaImportsAnalyzer.java
new file mode 100644
index 0000000..23a94e4
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/JavaImportsAnalyzer.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.java.compiler;
+
+/**
+ * The {@code JavaImportsAnalyzer} allows checking imports in generated HTL Java classes, in order to optimise dependencies.
+ */
+public interface JavaImportsAnalyzer {
+
+    /**
+     * Analyses the provided {@code importedClass} and decides if this class should be an explicit import or not in the generated HTL
+     * Java class.
+     *
+     * @param importedClass the import to analyse
+     * @return {@code true} if the import should be declared, {@code false} otherwise
+     */
+    default boolean allowImport(String importedClass) {
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/CodeGenVisitor.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/CodeGenVisitor.java
index a619ff6..629f9ac 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/CodeGenVisitor.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/CodeGenVisitor.java
@@ -117,7 +117,7 @@
     @Override
     public void visit(VariableBinding.Start variableBinding) {
         source.startBlock();
-        TypeInfo typeInfo = TypeInference.inferTypes(variableBinding.getExpression(), analyzer);
+        TypeInfo typeInfo = TypeInference.inferTypes(variableBinding.getExpression(), analyzer, unitBuilder.getImports());
         Type type = typeInfo.typeOf(variableBinding.getExpression());
         String properName = declare(variableBinding.getVariableName(), type);
         source.beginAssignment(properName, type.getNativeClass());
@@ -142,7 +142,7 @@
 
     @Override
     public void visit(VariableBinding.Global globalAssignment) {
-        TypeInfo typeInfo = TypeInference.inferTypes(globalAssignment.getExpression(), analyzer);
+        TypeInfo typeInfo = TypeInference.inferTypes(globalAssignment.getExpression(), analyzer, unitBuilder.getImports());
         VariableDescriptor descriptor = analyzer.declareGlobal(globalAssignment.getVariableName());
         String name = descriptor.getAssignedName();
         source.append(name).assign();
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/JavaClassTemplate.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/JavaClassTemplate.java
index 34be88b..85a0236 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/JavaClassTemplate.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/JavaClassTemplate.java
@@ -23,10 +23,10 @@
 
 
     private String classTemplate;
-
-    private static final String MAIN_BODY = "MainBody";
-    private static final String CLASS_NAME = "ClassName";
     private static final String PACKAGE_NAME = "PackageName";
+    private static final String IMPORTS = "Imports";
+    private static final String CLASS_NAME = "ClassName";
+    private static final String MAIN_BODY = "MainBody";
     private static final String TEMPLATE_INIT = "SubTemplateMapInit";
     private static final String NAME = "Name";
 
@@ -52,6 +52,10 @@
         setPart(PACKAGE_NAME, name);
     }
 
+    public void setImports(String imports) {
+        setPart(IMPORTS, imports);
+    }
+
     @Override
     public String toString() {
         return insertPart(TEMPLATE_INIT, classTemplate, templateInitBuilder.toString());
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/Type.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/Type.java
index e73d5e5..187c650 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/Type.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/Type.java
@@ -20,27 +20,38 @@
 /**
  * Type inferred for an expression
  */
-public enum Type {
-    UNKNOWN("Object", false),
-    STRING("String", false),
-    LONG("long", true),
-    DOUBLE("double", true),
-    BOOLEAN("boolean", true),
-    MAP("java.util.Map", false);
+public class Type {
 
-    private final String nativeClass;
+    public static final Type UNKNOWN = new Type("Object", false, null);
+    public static final Type STRING = new Type("String", false, null);
+    public static final Type LONG = new Type("long", true, 0);
+    public static final Type DOUBLE = new Type("double", true, 0.0d);
+    public static final Type BOOLEAN = new Type("boolean", true, false);
+    public static final Type MAP = new Type("java.util.Map", false, null);
+
+    private String nativeClass;
     private final boolean isPrimitive;
+    private final Object defaultValue;
 
-    Type(String nativeClass, boolean isPrimitive) {
+    private Type(String nativeClass, boolean isPrimitive, Object defaultValue) {
         this.nativeClass = nativeClass;
         this.isPrimitive = isPrimitive;
+        this.defaultValue = defaultValue;
     }
 
     public String getNativeClass() {
         return nativeClass;
     }
 
+    public Object getDefaultValue() {
+        return defaultValue;
+    }
+
     public boolean isPrimitive() {
         return isPrimitive;
     }
+
+    public static Type dynamic(String type) {
+        return new Type(type, false, null);
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/TypeInference.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/TypeInference.java
index 39377b8..6511de3 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/TypeInference.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/TypeInference.java
@@ -19,10 +19,10 @@
 
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
 
-import org.apache.sling.scripting.sightly.java.compiler.impl.operator.BinaryOpGen;
-import org.apache.sling.scripting.sightly.java.compiler.impl.operator.Operators;
-import org.apache.sling.scripting.sightly.java.compiler.impl.operator.UnaryOpGen;
+import org.apache.sling.scripting.sightly.compiler.RuntimeFunction;
 import org.apache.sling.scripting.sightly.compiler.expression.ExpressionNode;
 import org.apache.sling.scripting.sightly.compiler.expression.NodeVisitor;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.ArrayLiteral;
@@ -37,6 +37,9 @@
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.StringConstant;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.TernaryOperator;
 import org.apache.sling.scripting.sightly.compiler.expression.nodes.UnaryOperation;
+import org.apache.sling.scripting.sightly.java.compiler.impl.operator.BinaryOpGen;
+import org.apache.sling.scripting.sightly.java.compiler.impl.operator.Operators;
+import org.apache.sling.scripting.sightly.java.compiler.impl.operator.UnaryOpGen;
 
 /**
  * Expression translator which uses type information
@@ -44,17 +47,20 @@
 public final class TypeInference implements NodeVisitor<Type> {
 
     private final VariableAnalyzer analyzer;
-    private final Map<ExpressionNode, Type> inferMap = new IdentityHashMap<ExpressionNode, Type>();
+    private final Map<ExpressionNode, Type> inferMap = new IdentityHashMap<>();
+    private final Set<String> imports;
+    private static final Pattern FQCN_PATTERN = Pattern.compile("([[\\p{L}&&[^\\p{Lu}]]_$][\\p{L}\\p{N}_$]*\\.)+[\\p{Lu}_$][\\p{L}\\p{N}_$]*");
 
 
-    public static TypeInfo inferTypes(ExpressionNode node, VariableAnalyzer analyzer) {
-        TypeInference typeInference = new TypeInference(analyzer);
+    public static TypeInfo inferTypes(ExpressionNode node, VariableAnalyzer analyzer, Set<String> imports) {
+        TypeInference typeInference = new TypeInference(analyzer, imports);
         typeInference.infer(node);
         return new TypeInfo(typeInference.inferMap);
     }
 
-    private TypeInference(VariableAnalyzer analyzer) {
+    private TypeInference(VariableAnalyzer analyzer, Set<String> imports) {
         this.analyzer = analyzer;
+        this.imports = imports;
     }
 
 
@@ -128,6 +134,16 @@
     @Override
     public Type evaluate(RuntimeCall runtimeCall) {
         inferAll(runtimeCall.getArguments());
+        if (runtimeCall.getFunctionName().equals(RuntimeFunction.USE)) {
+            ExpressionNode identifier = runtimeCall.getArguments().get(0);
+            if (identifier instanceof StringConstant) {
+                String objectType = ((StringConstant) identifier).getText();
+                if (FQCN_PATTERN.matcher(objectType).matches()) {
+                    imports.add(objectType);
+                    return Type.dynamic(objectType);
+                }
+            }
+        }
         return Type.UNKNOWN;
     }
 
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/UnitBuilder.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/UnitBuilder.java
index 990bf8c..3a7678c 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/UnitBuilder.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/UnitBuilder.java
@@ -19,10 +19,12 @@
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
 import org.apache.sling.scripting.sightly.java.compiler.CompilationOutput;
+import org.apache.sling.scripting.sightly.java.compiler.JavaImportsAnalyzer;
 
 /**
  * Builder for compiled sources
@@ -32,9 +34,12 @@
     private final JavaSource source = new JavaSource();
     private final Set<String> parameters;
     private final Map<String, UnitBuilder> subTemplates = new HashMap<String, UnitBuilder>();
+    private Set<String> imports = new HashSet<>();
+    private JavaImportsAnalyzer javaImportsAnalyzer;
 
-    public UnitBuilder() {
-        this(Collections.<String>emptySet());
+    public UnitBuilder(JavaImportsAnalyzer javaImportsAnalyzer) {
+        this(Collections.emptySet());
+        this.javaImportsAnalyzer = javaImportsAnalyzer;
     }
 
     public UnitBuilder(Set<String> parameters) {
@@ -62,4 +67,12 @@
         }
         return new CompilationOutputImpl(source.toString(), map);
     }
+
+    public JavaImportsAnalyzer getJavaImportsAnalyzer() {
+        return javaImportsAnalyzer;
+    }
+
+    public Set<String> getImports() {
+        return imports;
+    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/VariableDescriptor.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/VariableDescriptor.java
index 51193d6..800a5bd 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/VariableDescriptor.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/impl/VariableDescriptor.java
@@ -78,7 +78,7 @@
     }
 
     /**
-     * Get the inferred type fot this variable
+     * Get the inferred type for this variable
      * @return - the class of the variable
      */
     public Type getType() {
diff --git a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/package-info.java
index 4502fd2..45ddcbe 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/java/compiler/package-info.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/java/compiler/package-info.java
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  ******************************************************************************/
-@Version("1.1.0")
+@Version("1.2.0")
 package org.apache.sling.scripting.sightly.java.compiler;
 
 import org.osgi.annotation.versioning.Version;
diff --git a/src/main/resources/templates/compiled_unit_template.txt b/src/main/resources/templates/compiled_unit_template.txt
index a327d6a..3ce6585 100644
--- a/src/main/resources/templates/compiled_unit_template.txt
+++ b/src/main/resources/templates/compiled_unit_template.txt
@@ -25,6 +25,8 @@
 import org.apache.sling.scripting.sightly.java.compiler.RenderUnit;
 import org.apache.sling.scripting.sightly.render.RenderContext;
 
+##Imports##
+
 public final class ##ClassName## extends RenderUnit {
 
     @Override
diff --git a/src/test/java/org/apache/sling/scripting/sightly/compiler/java/JavaClassBackendCompilerTest.java b/src/test/java/org/apache/sling/scripting/sightly/compiler/java/JavaClassBackendCompilerTest.java
index 3dd3c8c..924e519 100644
--- a/src/test/java/org/apache/sling/scripting/sightly/compiler/java/JavaClassBackendCompilerTest.java
+++ b/src/test/java/org/apache/sling/scripting/sightly/compiler/java/JavaClassBackendCompilerTest.java
@@ -93,6 +93,18 @@
         assertEquals(expectedOutput, writer.toString());
     }
 
+    @Test
+    public void generateImportStatements() throws Exception {
+        CompilationUnit compilationUnit = TestUtils.readScriptFromClasspath("/imports.html");
+        JavaClassBackendCompiler backendCompiler = new JavaClassBackendCompiler();
+        SightlyCompiler sightlyCompiler = new SightlyCompiler();
+        sightlyCompiler.compile(compilationUnit, backendCompiler);
+        ClassInfo classInfo = buildClassInfo("imports");
+        String source = backendCompiler.build(classInfo);
+        String expectedJavaOutput = IOUtils.toString(this.getClass().getResourceAsStream("/imports.html.java"), "UTF-8");
+        assertEquals(expectedJavaOutput, source);
+    }
+
     private ClassInfo buildClassInfo(final String info) {
         return new ClassInfo() {
             @Override
diff --git a/src/test/resources/imports.html b/src/test/resources/imports.html
new file mode 100644
index 0000000..82d1905
--- /dev/null
+++ b/src/test/resources/imports.html
@@ -0,0 +1,22 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+<div data-sly-use.slingSettings="org.apache.sling.settings.SlingSettingsService"
+     data-sly-use.js="script.js"
+     data-sly-use.pojo="Pojo"
+></div>
diff --git a/src/test/resources/imports.html.java b/src/test/resources/imports.html.java
new file mode 100644
index 0000000..f80f7db
--- /dev/null
+++ b/src/test/resources/imports.html.java
@@ -0,0 +1,64 @@
+/*******************************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ ******************************************************************************/
+package org.apache.sling.scripting.sightly.compiler.java;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import javax.script.Bindings;
+
+import org.apache.sling.scripting.sightly.java.compiler.RenderUnit;
+import org.apache.sling.scripting.sightly.render.RenderContext;
+
+import org.apache.sling.settings.SlingSettingsService;
+
+
+public final class Test_imports extends RenderUnit {
+
+    @Override
+    protected final void render(PrintWriter out,
+                                Bindings bindings,
+                                Bindings arguments,
+                                RenderContext renderContext) {
+// Main Template Body -----------------------------------------------------------------------------
+
+Object _global_slingsettings = null;
+Object _global_js = null;
+Object _global_pojo = null;
+out.write("\n");
+_global_slingsettings = renderContext.call("use", "org.apache.sling.settings.SlingSettingsService", obj());
+_global_js = renderContext.call("use", "script.js", obj());
+_global_pojo = renderContext.call("use", "Pojo", obj());
+out.write("<div></div>\n");
+
+
+// End Of Main Template Body ----------------------------------------------------------------------
+    }
+
+
+
+    {
+//Sub-Templates Initialization --------------------------------------------------------------------
+
+
+
+//End of Sub-Templates Initialization -------------------------------------------------------------
+    }
+
+}
+