You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2019/01/22 09:41:17 UTC
[sling-org-apache-sling-scripting-sightly-runtime] 01/11:
SLING-8012 - Extract an HTL runtime bundle from the existing HTL modules
This is an automated email from the ASF dual-hosted git repository.
radu pushed a commit to branch issue/SLING-8228
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-runtime.git
commit ef2d6e1ef567e3543de3078ec2251463486b100e
Author: Radu Cotescu <co...@adobe.com>
AuthorDate: Thu Oct 18 13:42:40 2018 +0200
SLING-8012 - Extract an HTL runtime bundle from the existing HTL modules
* initial commit
---
.gitignore | 17 +
CODE_OF_CONDUCT.md | 22 +
CONTRIBUTING.md | 24 ++
LICENSE | 202 +++++++++
README.md | 9 +
pom.xml | 192 +++++++++
.../org/apache/sling/scripting/sightly/Record.java | 51 +++
.../sling/scripting/sightly/SightlyException.java | 38 ++
.../sightly/extension/RuntimeExtension.java | 170 ++++++++
.../scripting/sightly/extension/package-info.java | 22 +
.../sling/scripting/sightly/package-info.java | 21 +
.../apache/sling/scripting/sightly/pojo/Use.java | 47 ++
.../sling/scripting/sightly/pojo/package-info.java | 22 +
.../sightly/render/AbstractRuntimeObjectModel.java | 143 ++++++
.../scripting/sightly/render/ObjectModel.java | 480 +++++++++++++++++++++
.../scripting/sightly/render/RenderContext.java | 51 +++
.../sling/scripting/sightly/render/RenderUnit.java | 156 +++++++
.../sightly/render/RuntimeObjectModel.java | 121 ++++++
.../scripting/sightly/render/package-info.java | 20 +
.../scripting/sightly/use/ProviderOutcome.java | 133 ++++++
.../sling/scripting/sightly/use/UseProvider.java | 46 ++
.../sling/scripting/sightly/use/package-info.java | 22 +
.../render/AbstractRuntimeObjectModelTest.java | 168 ++++++++
.../scripting/sightly/render/ObjectModelTest.java | 245 +++++++++++
.../sightly/render/testobjects/Person.java | 29 ++
.../sightly/render/testobjects/TestEnum.java | 25 ++
.../testobjects/internal/AbstractPerson.java | 42 ++
.../sightly/render/testobjects/internal/Adult.java | 32 ++
.../render/testobjects/internal/AdultFactory.java | 26 ++
29 files changed, 2576 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5b783ed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+/target
+.idea
+.classpath
+.metadata
+.project
+.settings
+.externalToolBuilders
+maven-eclipse.xml
+*.swp
+*.iml
+*.ipr
+*.iws
+*.bak
+.vlt
+.DS_Store
+jcr.log
+atlassian-ide-plugin.xml
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..0fa18e5
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+Apache Software Foundation Code of Conduct
+====
+
+Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ac82a1a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+Contributing
+====
+
+Thanks for choosing to contribute!
+
+You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9fa5bc8
--- /dev/null
+++ b/README.md
@@ -0,0 +1,9 @@
+[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org)
+
+ [![Build Status](https://builds.apache.org/buildStatus/icon?job=sling-org-apache-sling-scripting-sightly-runtime-1.8)](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-scripting-sightly-runtime-1.8) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-scripting-sightly-runtime-1.8.svg)](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-scripting-sightly-runtime-1.8/test_resu [...]
+
+# Apache Sling Scripting HTL Runtime
+
+This module is part of the [Apache Sling](https://sling.apache.org) project.
+
+The Apache Sling Scripting HTL Runtime provides support for executing HTL Java compiled units produced by the [`org.apache.sling.scripting.sightly.compiler.java module`](https://github.com/apache/sling-org-apache-sling-scripting-sightly-runtime).
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..0b404db
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <!-- ======================================================================= -->
+ <!-- P A R E N T P R O J E C T -->
+ <!-- ======================================================================= -->
+ <parent>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>sling</artifactId>
+ <version>30</version>
+ <relativePath />
+ </parent>
+
+ <!-- ======================================================================= -->
+ <!-- P R O J E C T -->
+ <!-- ======================================================================= -->
+ <artifactId>org.apache.sling.scripting.sightly.runtime</artifactId>
+ <!--
+ The versioning scheme defined here corresponds to SLING-7406 (<module_version>-<htl_specification_version>). Take care when
+ releasing to only increase the first part, unless the module provides support for a newer version of the HTL specification.
+ -->
+ <version>1.0.0-1.4.0-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Sling Scripting HTL Runtime</name>
+
+ <description>
+ The Apache Sling Scripting HTL Runtime provides support for executing HTL Java compiled units produced by the org.apache.sling.scripting.sightly.compiler.java module.
+ </description>
+
+ <scm>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-runtime.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-runtime.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-scripting-sightly-runtime.git</url>
+ <tag>HEAD</tag>
+ </scm>
+
+ <properties>
+ <jacoco.maven.plugin.version>0.7.9</jacoco.maven.plugin.version>
+ <sling.java.version>8</sling.java.version>
+ </properties>
+
+ <!-- ======================================================================= -->
+ <!-- B U I L D -->
+ <!-- ======================================================================= -->
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>scr-metadata</id>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <exportScr>true</exportScr>
+ <instructions>
+ <Provide-Capability>
+ io.sightly.runtime; version:Version=1.0,
+ io.sightly.runtime; version:Version=1.1,
+ io.sightly.runtime; version:Version=1.2,
+ io.sightly.runtime; version:Version=1.3,
+ io.sightly.runtime; version:Version=1.3.1,
+ io.sightly.runtime; version:Version=1.4
+ </Provide-Capability>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>findbugs-maven-plugin</artifactId>
+ <version>3.0.3</version>
+ <configuration>
+ <effort>Max</effort>
+ <xmlOutput>true</xmlOutput>
+ </configuration>
+ <executions>
+ <execution>
+ <id>find-bugs</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <configuration>
+ <!-- No javadocs -->
+ <excludePackageNames>
+ org.apache.sling.scripting.sightly.java.compiler.impl
+ </excludePackageNames>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>coverage-report</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>${jacoco.maven.plugin.version}</version>
+ <executions>
+ <execution>
+ <id>default-prepare-agent</id>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-report</id>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>org/apache/sling/scripting/sightly/**/*</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <!-- ======================================================================= -->
+ <!-- D E P E N D E N C I E S -->
+ <!-- ======================================================================= -->
+ <dependencies>
+
+ <!-- Logging -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Apache Commons -->
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/Record.java b/src/main/java/org/apache/sling/scripting/sightly/Record.java
new file mode 100644
index 0000000..5c8be4a
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/Record.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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;
+
+import java.util.Set;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * A {@code Record} is a key-value immutable object understood by the HTL runtime, used for abstracting complex objects like Sightly
+ * templates (declared with the {@code data-sly-template} block element) or objects that need to be translated from Java to JavaScript
+ * and back.
+ *
+ * @param <T> the type of values for this record
+ */
+@ConsumerType
+public interface Record<T> {
+
+ /**
+ * Gets the value of a specified property.
+ *
+ * @param name the name of the property
+ * @return the value of the property or {@code null} if this record does not have the specified property
+ */
+ T getProperty(String name);
+
+ /**
+ * Gets the set of names for this record's properties.
+ *
+ * @return this record's properties' names
+ */
+ Set<String> getPropertyNames();
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/SightlyException.java b/src/main/java/org/apache/sling/scripting/sightly/SightlyException.java
new file mode 100644
index 0000000..16111ee
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/SightlyException.java
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Exceptions caused by the HTL engine.
+ */
+public class SightlyException extends RuntimeException {
+
+ public SightlyException() {
+ }
+
+ public SightlyException(String message) {
+ super(message);
+ }
+
+ public SightlyException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public SightlyException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/extension/RuntimeExtension.java b/src/main/java/org/apache/sling/scripting/sightly/extension/RuntimeExtension.java
new file mode 100644
index 0000000..1cfaa0c
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/extension/RuntimeExtension.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * 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.extension;
+
+import org.apache.sling.scripting.sightly.render.RenderContext;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * A {@code RuntimeExtension} represents a HTL runtime construct that provides some processing capabilities for the various
+ * {@code data-sly-*} block elements.
+ */
+@ConsumerType
+public interface RuntimeExtension {
+
+ /**
+ * <p>
+ * The name of the runtime function that will process string
+ * formatting. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>the format String (e.g. 'Hello {0}, welcome to {1}')</li>
+ * <li>an array of objects that will replace the format placeholders</li>
+ * </ol>
+ * <p>
+ * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#122-format.
+ * </p>
+ */
+ String FORMAT = "format";
+
+ /**
+ * <p>
+ * The name of the runtime function that will process
+ * i18n. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>the String to translate</li>
+ * <li>optional: locale information</li>
+ * <li>optional: hint information</li>
+ * <li>optional (not part of the specification): basename information; for more details see
+ * {@link java.util.ResourceBundle#getBundle(String, java.util.Locale)}</li>
+ * </ol>
+ * <p>
+ * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#123-i18n.
+ * </p>
+ */
+ String I18N = "i18n";
+
+ /**
+ * <p>
+ * The name of the runtime function that will process
+ * join operations on arrays. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>the array of objects to join (e.g. [1, 2, 3])</li>
+ * <li>the join string (e.g. ';')</li>
+ * </ol>
+ * <p>
+ * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#124-array-join.
+ * </p>
+ */
+ String JOIN = "join";
+
+ /**
+ * <p>
+ * The name of the runtime function that will provide
+ * URI manipulation support. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>optional: a URI string to process</li>
+ * <li>optional: a Map containing URI manipulation options</li>
+ * </ol>
+ * <p>
+ * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#125-uri-manipulation.
+ * </p>
+ */
+ String URI_MANIPULATION = "uriManipulation";
+
+ /**
+ * <p>
+ * The name of the runtime function that will provide
+ * XSS escaping and filtering support. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>the original string to escape / filter</li>
+ * <li>the context to be applied - see {@link org.apache.sling.scripting.sightly.compiler.expression.MarkupContext}</li>
+ * </ol>
+ * <p>
+ * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#121-display-context.
+ * </p>
+ */
+ String XSS = "xss";
+
+ /**
+ * <p>
+ * The name of the runtime function that will perform
+ * script execution delegation. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>optional: the relative or absolute path of the script to execute</li>
+ * <li>optional: a Map of options to perform script include processing</li>
+ * </ol>
+ * <p>
+ * For more details about the supported options check
+ * https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#228-include.
+ * </p>
+ */
+ String INCLUDE = "include";
+
+ /**
+ * <p>
+ * The name of the runtime function that will perform
+ * resource inclusion in the rendering process. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>optional: a relative or absolute path of the resource to be included</li>
+ * <li>optional: a Map containing the resource processing options</li>
+ * </ol>
+ * <p>
+ * For more details about the supported options check
+ * https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#229-resource.
+ * </p>
+ */
+ String RESOURCE = "includeResource";
+
+ /**
+ * <p>
+ * The name of the runtime function that will provide
+ * the support for loading Use-API objects. The function will receive the following parameters:
+ * </p>
+ * <ol>
+ * <li>an identifier that allows to discover the Use-API object that needs to be loaded</li>
+ * <li>optional: a Map of the arguments that are passed to the Use-API object for initialisation or to provide context</li>
+ * </ol>
+ * <p>
+ * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#221-use.
+ * </p>
+ */
+ String USE = "use";
+
+ /**
+ * For OSGi environments this is the name of the service registration property indicating the {@code RuntimeExtension} name.
+ */
+ String NAME = "org.apache.sling.scripting.sightly.extension.name";
+
+ /**
+ * Call the {@code RuntimeExtension}
+ *
+ * @param renderContext the runtime context
+ * @param arguments the call arguments
+ * @return an extension instance
+ */
+ Object call(RenderContext renderContext, Object... arguments);
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/extension/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/extension/package-info.java
new file mode 100644
index 0000000..1bbe78a
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/extension/package-info.java
@@ -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.
+ ******************************************************************************/
+@Version("1.1.0")
+package org.apache.sling.scripting.sightly.extension;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/package-info.java
new file mode 100644
index 0000000..f2af1f3
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/package-info.java
@@ -0,0 +1,21 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+
+@Version("2.0.0")
+package org.apache.sling.scripting.sightly;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/pojo/Use.java b/src/main/java/org/apache/sling/scripting/sightly/pojo/Use.java
new file mode 100644
index 0000000..722b79a
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/pojo/Use.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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.pojo;
+
+import javax.script.Bindings;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The <code>Use</code> interface can be implemented by Java objects which are instantiated as part of processing {@code data-sly-use}
+ * attributes.
+ *
+ * @see <a href="https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/master/SPECIFICATION.md#221-use">HTL Block Statements - Use</a>
+ */
+@ConsumerType
+public interface Use {
+
+ /**
+ * <p>
+ * Called to initialize the Java object with the current Java Scripting API bindings.
+ * </p>
+ * <p>
+ * This method is called only if the object has been instantiated by HTL as part of processing the {@code data-sly-use}
+ * attribute. The Java Scripting API bindings provide all the global variables known to a script being executed.
+ * </p>
+ *
+ * @param bindings The Java Scripting API bindings.
+ */
+ void init(Bindings bindings);
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/pojo/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/pojo/package-info.java
new file mode 100644
index 0000000..746ab8f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/pojo/package-info.java
@@ -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.
+ ******************************************************************************/
+@Version("1.0.1")
+package org.apache.sling.scripting.sightly.pojo;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModel.java
new file mode 100644
index 0000000..858693e
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModel.java
@@ -0,0 +1,143 @@
+/*******************************************************************************
+ * 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.render;
+
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.sling.scripting.sightly.Record;
+
+/**
+ * Default abstract implementation of {@link RuntimeObjectModel}.
+ */
+public abstract class AbstractRuntimeObjectModel implements RuntimeObjectModel {
+
+ @Override
+ public boolean isPrimitive(Object obj) {
+ return ObjectModel.isPrimitive(obj);
+ }
+
+ @Override
+ public boolean isDate(Object target) {
+ return (target instanceof Date || target instanceof Calendar);
+ }
+
+ @Override
+ public boolean isNumber(Object target) {
+ if (target == null) {
+ return false;
+ }
+ if (target instanceof Number) {
+ return true;
+ }
+ String value = toString(target);
+ return NumberUtils.isCreatable(value);
+ }
+
+ @Override
+ public boolean isCollection(Object target) {
+ return (target instanceof Collection) || (target instanceof Object[]) || (target instanceof Iterable) ||
+ (target instanceof Iterator);
+ }
+
+ @Override
+ public Object resolveProperty(Object target, Object property) {
+ if (target == null || property == null) {
+ return null;
+ }
+ Object resolved = null;
+ if (property instanceof Number) {
+ resolved = ObjectModel.getIndex(target, ((Number) property).intValue());
+ }
+ if (resolved == null) {
+ resolved = getProperty(target, property);
+ }
+ return resolved;
+ }
+
+ @Override
+ public boolean toBoolean(Object object) {
+ return ObjectModel.toBoolean(object);
+ }
+
+ @Override
+ public Number toNumber(Object object) {
+ return ObjectModel.toNumber(object);
+ }
+
+ @Override
+ public Date toDate(Object object) {
+ if (object instanceof Date) {
+ return (Date)object;
+ } else if (object instanceof Calendar) {
+ return ((Calendar)object).getTime();
+ }
+ return null;
+ }
+
+ @Override
+ public String toString(Object target) {
+ return ObjectModel.toString(target);
+ }
+
+ @Override
+ public Collection<Object> toCollection(Object object) {
+ if (object instanceof Record) {
+ return ((Record) object).getPropertyNames();
+ }
+ return ObjectModel.toCollection(object);
+ }
+
+ @Override
+ public Map toMap(Object object) {
+ if (object instanceof Map) {
+ return (Map) object;
+ } else if (object instanceof Record) {
+ Map<String, Object> map = new HashMap<>();
+ Record record = (Record) object;
+ Set<String> properties = record.getPropertyNames();
+ for (String property : properties) {
+ map.put(property, record.getProperty(property));
+ }
+ return map;
+ }
+ return Collections.emptyMap();
+ }
+
+ protected Object getProperty(Object target, Object propertyObj) {
+ if (target == null || propertyObj == null) {
+ return null;
+ }
+ String property = ObjectModel.toString(propertyObj);
+ Object result = null;
+ if (target instanceof Record) {
+ result = ((Record) target).getProperty(property);
+ }
+ if (result == null) {
+ result = ObjectModel.resolveProperty(target, propertyObj);
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/ObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/render/ObjectModel.java
new file mode 100644
index 0000000..6b437d7
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/render/ObjectModel.java
@@ -0,0 +1,480 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@code ObjectModel} class provides various static models for object conversion and object property resolution.
+ */
+public final class ObjectModel {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ObjectModel.class);
+ private static final String EMPTY_STRING = "";
+
+ /**
+ * A {@link Set} that stores all the supported primitive classes.
+ */
+ private static final Set<Class<?>> PRIMITIVE_CLASSES;
+
+ static {
+ Set<Class<?>> primitivesBuilder = new HashSet<>();
+ primitivesBuilder.add(Boolean.class);
+ primitivesBuilder.add(Character.class);
+ primitivesBuilder.add(Byte.class);
+ primitivesBuilder.add(Short.class);
+ primitivesBuilder.add(Integer.class);
+ primitivesBuilder.add(Long.class);
+ primitivesBuilder.add(Float.class);
+ primitivesBuilder.add(Double.class);
+ primitivesBuilder.add(Void.class);
+ PRIMITIVE_CLASSES = Collections.unmodifiableSet(primitivesBuilder);
+ }
+
+
+ private static final String TO_STRING_METHOD = "toString";
+
+ private ObjectModel() {}
+
+ /**
+ * Checks if the provided {@code object} is an instance of a primitive class.
+ *
+ * @param object the {@code Object} to check
+ * @return {@code true} if the {@code object} is a primitive, {@code false} otherwise
+ */
+ public static boolean isPrimitive(Object object) {
+ return PRIMITIVE_CLASSES.contains(object.getClass());
+ }
+
+ /**
+ * <p>
+ * Given the {@code target} object, this method attempts to resolve and return the value of the passed {@code property}.
+ * </p>
+ * <p>
+ * The property can be either an index or a name:
+ * </p>
+ * <ul>
+ * <li>index: the property is considered an index if its value is an integer number and in this case the {@code target}
+ * will be assumed to be either an array or it will be converted to a {@link Collection}; a fallback to {@link Map} will be
+ * made in case the previous two attempts failed
+ * </li>
+ * <li>name: the {@code property} will be converted to a {@link String} (see {@link #toString(Object)}); the {@code target}
+ * will be assumed to be either a {@link Map} or an object; if the {@link Map} attempt fails, the {@code property} will be
+ * used to check if the {@code target} has a publicly accessible field with this name or a publicly accessible method with no
+ * parameters with this name or a combination of the "get" or "is" prefixes plus the capitalised name (see
+ * {@link #invokeBeanMethod(Object, String)})</li>
+ * </ul>
+ *
+ * @param target the target object
+ * @param property the property to be resolved
+ * @return the value of the property or {@code null}
+ */
+ public static Object resolveProperty(Object target, Object property) {
+ if (target == null || property == null) {
+ return null;
+ }
+ Object resolved = null;
+ if (property instanceof Number) {
+ resolved = getIndex(target, ((Number) property).intValue());
+ }
+ if (resolved == null) {
+ String propertyName = toString(property);
+ if (StringUtils.isNotEmpty(propertyName)) {
+ if (target instanceof Map) {
+ resolved = ((Map) target).get(property);
+ }
+ if (resolved == null) {
+ resolved = getField(target, propertyName);
+ }
+ if (resolved == null) {
+ resolved = invokeBeanMethod(target, propertyName);
+ }
+ }
+ }
+ return resolved;
+ }
+
+ /**
+ * Converts the given {@code object} to a boolean value, applying the following rules:
+ *
+ * <ul>
+ * <li>if the {@code object} is {@code null} the returned value is {@code false}</li>
+ * <li>if the {@code object} is a {@link Number} the method will return {@code false} only if the number's value is 0</li>
+ * <li>if the {@link String} representation of the {@code object} is equal irrespective of its casing to "true", the method will
+ * return {@code true}</li>
+ * <li>if the {@code object} is a {@link Collection} or a {@link Map}, the method will return {@code true} only if the collection /
+ * map is not empty</li>
+ * <li>if the object is an array, the method will return {@code true} only if the array is not empty</li>
+ * </ul>
+ *
+ * @param object the target object
+ * @return the boolean representation of the {@code object} according to the conversion rules
+ */
+ public static boolean toBoolean(Object object) {
+ if (object == null) {
+ return false;
+ }
+
+ if (object instanceof Number) {
+ Number number = (Number) object;
+ return !(number.doubleValue() == 0.0);
+ }
+
+ String s = object.toString().trim();
+ if (EMPTY_STRING.equals(s)) {
+ return false;
+ } else if ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) {
+ return Boolean.parseBoolean(s);
+ }
+
+ if (object instanceof Collection) {
+ return ((Collection) object).size() > 0;
+ }
+
+ if (object instanceof Map) {
+ return ((Map) object).size() > 0;
+ }
+
+ if (object instanceof Iterable<?>) {
+ return ((Iterable<?>) object).iterator().hasNext();
+ }
+
+ if (object instanceof Iterator<?>) {
+ return ((Iterator<?>) object).hasNext();
+ }
+
+ return !(object instanceof Object[]) || ((Object[]) object).length > 0;
+ }
+
+ /**
+ * Coerces the passed {@code object} to a numeric value. If the passed value is a {@link String} the conversion rules are those of
+ * {@link NumberUtils#createNumber(String)}.
+ *
+ * @param object the target object
+ * @return the numeric representation if one can be determined or {@code null}
+ * @see NumberUtils#createNumber(String)
+ */
+ public static Number toNumber(Object object) {
+ if (object == null) {
+ return null;
+ }
+ if (object instanceof Number) {
+ return (Number) object;
+ }
+ String stringValue = toString(object);
+ try {
+ return NumberUtils.createNumber(stringValue);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Converts the passed {@code object} to a {@link String}. The following rules apply:
+ *
+ * <ul>
+ * <li>if the {@code object} is {@code null} an empty string will be returned</li>
+ * <li>if the {@code object} is an instance of a {@link String} the object itself will be returned</li>
+ * <li>if the object is a primitive (see {@link #isPrimitive(Object)}), its {@link String} representation will be returned</li>
+ * <li>if the object is an {@link Enum} its name will be returned (see {@link Enum#name()})</li>
+ * <li>otherwise an attempt to convert the object to a {@link Collection} will be made and then the output of
+ * {@link #collectionToString(Collection)} will be returned</li>
+ * </ul>
+ *
+ * @param object the target object
+ * @return the string representation of the object or an empty string
+ */
+ public static String toString(Object object) {
+ String output = EMPTY_STRING;
+ if (object != null) {
+ if (object instanceof String) {
+ output = (String) object;
+ } else if (isPrimitive(object)) {
+ output = object.toString();
+ } else if (object instanceof Enum) {
+ return ((Enum) object).name();
+ } else {
+ Collection<?> col = toCollection(object);
+ output = collectionToString(col);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Forces the conversion of the passed {@code object} to a collection, according to the following rules:
+ *
+ * <ul>
+ * <li>if the {@code object} is {@code null} an empty collection will be returned</li>
+ * <li>if the {@code object} is an array a list transformation of the array will be returned</li>
+ * <li>if the {@code object} is a {@link Collection} the object itself will be returned</li>
+ * <li>if the {@code object} is an instance of a {@link Map} the map's key set will be returned (see {@link Map#keySet()})</li>
+ * <li>if the {@code object} is an instance of an {@link Enumeration} a list transformation will be returned</li>
+ * <li>if the {@code object} is an instance of an {@link Iterator} or {@link Iterable} the result of {@link #fromIterator(Iterator)}
+ * will be returned</li>
+ * <li>if the {@code object} is an instance of a {@link String} or {@link Number} a {@link Collection} containing only this
+ * object will be returned</li>
+ * <li>any other case not covered by the previous rules will result in an empty {@link Collection}</li>
+ * </ul>
+ *
+ * @param object the target object
+ * @return the collection representation of the object
+ */
+ public static Collection<Object> toCollection(Object object) {
+ if (object == null) {
+ return Collections.emptyList();
+ }
+ if (object instanceof Object[]) {
+ return Arrays.asList((Object[]) object);
+ }
+ if (object.getClass().isArray()) {
+ int length = Array.getLength(object);
+ Collection<Object> list = new ArrayList<>();
+ for (int i = 0; i < length; i++) {
+ list.add(Array.get(object, i));
+ }
+ return list;
+ }
+ if (object instanceof Collection) {
+ return (Collection<Object>) object;
+ }
+ if (object instanceof Map) {
+ return ((Map) object).keySet();
+ }
+ if (object instanceof Enumeration) {
+ return Collections.list((Enumeration<Object>) object);
+ }
+ if (object instanceof Iterator) {
+ return fromIterator((Iterator<Object>) object);
+ }
+ if (object instanceof Iterable) {
+ Iterable<Object> iterable = (Iterable<Object>) object;
+ return fromIterator(iterable.iterator());
+ }
+ if (object instanceof String || object instanceof Number) {
+ Collection<Object> list = new ArrayList<>();
+ list.add(object);
+ return list;
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Converts the passed {@code collection} to a comma separated values {@link String} representation.
+ *
+ * @param collection the collection to be converted to CSV
+ * @return the CSV; if the {@code collection} is empty then an empty string will be returned
+ */
+ public static String collectionToString(Collection<?> collection) {
+ if (collection == null) {
+ return EMPTY_STRING;
+ }
+ StringBuilder builder = new StringBuilder();
+ String prefix = EMPTY_STRING;
+ for (Object o : collection) {
+ builder.append(prefix).append(toString(o));
+ prefix = ",";
+ }
+ return builder.toString();
+ }
+
+ /**
+ * Given an {@code iterator}, this method will return a {@link Collection}.
+ *
+ * @param iterator the iterator to be transformed into a {@code collection}
+ * @return a collection with the iterator's elements
+ */
+ public static Collection<Object> fromIterator(Iterator<Object> iterator) {
+ if (iterator == null) {
+ return Collections.EMPTY_LIST;
+ }
+ ArrayList<Object> result = new ArrayList<>();
+ while (iterator.hasNext()) {
+ result.add(iterator.next());
+ }
+ return result;
+ }
+
+ /**
+ * Given an indexable {@code object} (i.e. an array or a collection), this method will return the value available at the {@code
+ * index} position.
+ *
+ * @param object the indexable object
+ * @param index the index
+ * @return the value stored at the {@code index} or {@code null}
+ */
+ public static Object getIndex(Object object, int index) {
+ if (object == null) {
+ return null;
+ }
+ Class<?> cls = object.getClass();
+ if (cls.isArray() && index >= 0 && index < Array.getLength(object)) {
+ return Array.get(object, index);
+ }
+ Collection collection = toCollection(object);
+ if (collection instanceof List && index >= 0 && index < collection.size()) {
+ return ((List) collection).get(index);
+ }
+ return null;
+ }
+
+ /**
+ * Given an {@code object}, this method will return the value of the public field identified by {@code fieldName}.
+ *
+ * @param object the target object
+ * @param fieldName the name of the field
+ * @return the value of the field or {@code null} if the field was not found
+ */
+ public static Object getField(Object object, String fieldName) {
+ if (object == null || StringUtils.isEmpty(fieldName)) {
+ return null;
+ }
+ Class<?> cls = object.getClass();
+ if (cls.isArray() && "length".equals(fieldName)) {
+ return Array.getLength(object);
+ }
+ try {
+ Field field = cls.getField(fieldName);
+ return field.get(object);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Given a bean {@code object}, this method will invoke the public method without parameters identified by {@code methodName} and
+ * return the invocation's result.
+ *
+ * @param object the target object
+ * @param methodName the name of the public method without parameters to invoke
+ * @return the invocation's result or {@code null} if such a method cannot be found
+ */
+ public static Object invokeBeanMethod(Object object, String methodName) {
+ if (object == null || StringUtils.isEmpty(methodName)) {
+ return null;
+ }
+ Class<?> cls = object.getClass();
+ Method method = findBeanMethod(cls, methodName);
+ if (method != null) {
+ try {
+ method = extractMethodInheritanceChain(cls, method);
+ return method.invoke(object);
+ } catch (Exception e) {
+ LOGGER.error("Cannot access method " + methodName + " on object " + object.toString(), e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Given a bean class and a base method name, this method will try to find a public method without parameters that is named:
+ * <ol>
+ * <li>{@code baseName}</li>
+ * <li>get + {@code BaseName}</li>
+ * <li>is + {@code BaseName}</li>
+ * </ol>
+ *
+ * @param cls the class into which to search for the method
+ * @param baseName the base method name
+ * @return a method that matches the criteria or {@code null}
+ */
+ public static Method findBeanMethod(Class<?> cls, String baseName) {
+ if (cls == null || StringUtils.isEmpty(baseName)) {
+ return null;
+ }
+ Method[] publicMethods = cls.getMethods();
+ String capitalized = StringUtils.capitalize(baseName);
+ for (Method method : publicMethods) {
+ if (method.getParameterTypes().length == 0) {
+ String methodName = method.getName();
+ if (baseName.equals(methodName) || ("get" + capitalized).equals(methodName) || ("is" + capitalized).equals(methodName)) {
+ if (isMethodAllowed(method)) {
+ return method;
+ }
+ break;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if the method is not one of the {@link Object}'s class declared methods, with the exception of
+ * {@link Object#toString()}.
+ *
+ * @param method the method to check
+ * @return {@code true} if the method is not one of the {@link Object}'s class declared methods, with the exception of
+ * {@link Object#toString()}, {@code false} otherwise
+ */
+ public static boolean isMethodAllowed(Method method) {
+ if (method == null) {
+ return false;
+ }
+ Class<?> declaringClass = method.getDeclaringClass();
+ return declaringClass != Object.class || TO_STRING_METHOD.equals(method.getName());
+ }
+
+ private static Method extractMethodInheritanceChain(Class type, Method method) {
+ if (method == null || Modifier.isPublic(type.getModifiers())) {
+ return method;
+ }
+ Class[] interfaces = type.getInterfaces();
+ Method parentMethod;
+ for (Class<?> iface : interfaces) {
+ parentMethod = getClassMethod(iface, method);
+ if (parentMethod != null) {
+ return parentMethod;
+ }
+ }
+ return getClassMethod(type.getSuperclass(), method);
+ }
+
+ private static Method getClassMethod(Class<?> type, Method method) {
+ try {
+ Method parentMethod = type.getMethod(method.getName(), method.getParameterTypes());
+ parentMethod = extractMethodInheritanceChain(parentMethod.getDeclaringClass(), parentMethod);
+ if (parentMethod != null) {
+ return parentMethod;
+ }
+ } catch (NoSuchMethodException e) {
+ // ignore - maybe we don't have access to that method or the method does not belong to the current type
+ }
+ return null;
+ }
+
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/RenderContext.java b/src/main/java/org/apache/sling/scripting/sightly/render/RenderContext.java
new file mode 100644
index 0000000..d57e4c8
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/render/RenderContext.java
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * 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.render;
+
+import javax.script.Bindings;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * The {@code RenderContext} defines the context for executing HTL scripts.
+ */
+@ProviderType
+public interface RenderContext {
+
+ /**
+ * Provides the {@link RuntimeObjectModel} that will be used for resolving objects' properties or type conversion / coercion.
+ *
+ * @return the RuntimeObjectModel
+ */
+ RuntimeObjectModel getObjectModel();
+
+ /**
+ * Returns the map of script bindings available to HTL scripts.
+ *
+ * @return the global bindings for a script
+ */
+ Bindings getBindings();
+
+ /**
+ * Call one of the registered {@link org.apache.sling.scripting.sightly.extension.RuntimeExtension}s.
+ *
+ * @param functionName the name under which the extension is registered
+ * @param arguments the extension's arguments
+ * @return the {@link org.apache.sling.scripting.sightly.extension.RuntimeExtension}'s result
+ */
+ Object call(String functionName, Object... arguments);
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/RenderUnit.java b/src/main/java/org/apache/sling/scripting/sightly/render/RenderUnit.java
new file mode 100644
index 0000000..617565f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/render/RenderUnit.java
@@ -0,0 +1,156 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.script.Bindings;
+import javax.script.SimpleBindings;
+
+import org.apache.sling.scripting.sightly.Record;
+
+/**
+ * Basic unit of rendering. This also extends the record interface. The properties for a unit are the sub-units.
+ */
+public abstract class RenderUnit implements Record<RenderUnit> {
+
+ private final Map<String, RenderUnit> subTemplates = new HashMap<>();
+
+ private Map<String, RenderUnit> siblings;
+
+ /**
+ * Render the main script template
+ *
+ * @param out the {@link PrintWriter} to which the commands are written
+ * @param renderContext the rendering context
+ * @param arguments the arguments for this unit
+ */
+ public final void render(PrintWriter out, RenderContext renderContext, Bindings arguments) {
+ Bindings globalBindings = renderContext.getBindings();
+ render(out, buildGlobalScope(globalBindings), new CaseInsensitiveBindings(arguments), renderContext);
+ }
+
+ @Override
+ public RenderUnit getProperty(String name) {
+ return subTemplates.get(name.toLowerCase());
+ }
+
+ @Override
+ public Set<String> getPropertyNames() {
+ return subTemplates.keySet();
+ }
+
+ protected abstract void render(PrintWriter out,
+ Bindings bindings,
+ Bindings arguments,
+ RenderContext renderContext);
+
+ @SuppressWarnings({"unused", "unchecked"})
+ protected void callUnit(PrintWriter out, RenderContext renderContext, Object templateObj, Object argsObj) {
+ if (!(templateObj instanceof RenderUnit)) {
+ if (templateObj == null) {
+ throw new RuntimeException("data-sly-call: expression evaluates to null.");
+ }
+ if (renderContext.getObjectModel().isPrimitive(templateObj)) {
+ throw new RuntimeException(
+ "data-sly-call: primitive \"" + templateObj.toString() + "\" does not represent a HTL template.");
+ } else if (templateObj instanceof String) {
+ throw new RuntimeException(
+ "data-sly-call: String '" + templateObj.toString() + "' does not represent a HTL template.");
+ }
+ throw new RuntimeException(
+ "data-sly-call: " + templateObj.getClass().getName() + " does not represent a HTL template.");
+ }
+ RenderUnit unit = (RenderUnit) templateObj;
+ Map<String, Object> argumentsMap = renderContext.getObjectModel().toMap(argsObj);
+ Bindings arguments = new SimpleBindings(Collections.unmodifiableMap(argumentsMap));
+ unit.render(out, renderContext, arguments);
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ protected FluentMap obj() {
+ return new FluentMap();
+ }
+
+ @SuppressWarnings("unused")
+ protected final void addSubTemplate(String name, RenderUnit renderUnit) {
+ renderUnit.setSiblings(subTemplates);
+ subTemplates.put(name.toLowerCase(), renderUnit);
+ }
+
+ private void setSiblings(Map<String, RenderUnit> siblings) {
+ this.siblings = siblings;
+ }
+
+ private Bindings buildGlobalScope(Bindings bindings) {
+ SimpleBindings simpleBindings = new SimpleBindings(bindings);
+ simpleBindings.putAll(bindings);
+ if (siblings != null) {
+ simpleBindings.putAll(siblings);
+ }
+ simpleBindings.putAll(subTemplates);
+ return new CaseInsensitiveBindings(simpleBindings);
+ }
+
+ protected static class FluentMap extends HashMap<String, Object> {
+
+ /**
+ * Fluent variant of put.
+ *
+ * @param name the name of the property
+ * @param value the value of the property
+ * @return this instance
+ */
+ public FluentMap with(String name, Object value) {
+ put(name, value);
+ return this;
+ }
+
+ }
+
+ private static final class CaseInsensitiveBindings extends SimpleBindings {
+
+ private CaseInsensitiveBindings(Map<String, Object> m) {
+ for (Entry<String, Object> entry : m.entrySet()) {
+ put(entry.getKey().toLowerCase(), entry.getValue());
+ }
+ }
+
+ @Override
+ public Object get(Object key) {
+ if (!(key instanceof String)) {
+ throw new ClassCastException("key should be a String");
+ }
+ return super.get(((String) key).toLowerCase());
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ if (!(key instanceof String)) {
+ throw new ClassCastException("key should be a String");
+ }
+ return super.containsKey(((String) key).toLowerCase());
+ }
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/RuntimeObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/render/RuntimeObjectModel.java
new file mode 100644
index 0000000..f386634
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/render/RuntimeObjectModel.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.render;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+import org.osgi.annotation.versioning.ProviderType;
+
+/**
+ * The {@code RuntimeObjectModel} provides various utility object inspection & conversion methods that can be applied to runtime
+ * objects when executing HTL scripts.
+ */
+@ProviderType
+public interface RuntimeObjectModel {
+
+ /**
+ * Checks if the provided object represents a primitive data type or not.
+ *
+ * @param obj the target object
+ * @return {@code true} if the {@code target} is a primitive, {@code false} otherwise
+ */
+ boolean isPrimitive(Object obj);
+
+ /**
+ * Checks if an object is a {@link Collection} or is backed by one.
+ *
+ * @param target the target object
+ * @return {@code true} if the {@code target} is a collection or is backed by one, {@code false} otherwise
+ */
+ boolean isCollection(Object target);
+
+ /**
+ * Checks if the provided object represents a number or not.
+ *
+ * @param target the target object
+ * @return {@code true} if the {@code target} is a number, {@code false} otherwise
+ */
+ boolean isNumber(Object target);
+
+ /**
+ * Checks if the provided object represents a date or calendar.
+ *
+ * @param target the target object
+ * @return {@code true} if the {@code target} is a date or calendar, {@code false} otherwise
+ */
+ boolean isDate(Object target);
+
+ /**
+ * Resolve a property of a target object and return its value. The property can
+ * be either an index or a name
+ *
+ * @param target the target object
+ * @param property the property to be resolved
+ * @return the value of the property
+ */
+ Object resolveProperty(Object target, Object property);
+
+ /**
+ * Convert the given object to a boolean value
+ *
+ * @param object the target object
+ * @return the boolean representation of that object
+ */
+ boolean toBoolean(Object object);
+
+ /**
+ * Coerce the object to a numeric value
+ *
+ * @param object the target object
+ * @return the numeric representation
+ */
+ Number toNumber(Object object);
+
+ /**
+ * Convert the given object to a {@link Date} object
+ *
+ * @param object the target object
+ * @return the date represented by the {@code object}
+ */
+ Date toDate(Object object);
+
+ /**
+ * Convert the given object to a string.
+ *
+ * @param target the target object
+ * @return the string representation of the object
+ */
+ String toString(Object target);
+
+ /**
+ * Force the conversion of the object to a collection
+ *
+ * @param object the target object
+ * @return the collection representation of the object
+ */
+ Collection<Object> toCollection(Object object);
+
+ /**
+ * Force the conversion of the target object to a map
+ *
+ * @param object the target object
+ * @return a map representation of the object. Default is an empty map
+ */
+ Map toMap(Object object);
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/render/package-info.java
new file mode 100644
index 0000000..3afbfb9
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/render/package-info.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * 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.
+ ******************************************************************************/
+@Version("3.0.0")
+package org.apache.sling.scripting.sightly.render;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/main/java/org/apache/sling/scripting/sightly/use/ProviderOutcome.java b/src/main/java/org/apache/sling/scripting/sightly/use/ProviderOutcome.java
new file mode 100644
index 0000000..83fb379
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/use/ProviderOutcome.java
@@ -0,0 +1,133 @@
+/*******************************************************************************
+ * 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.use;
+
+/**
+ * Result returned by a {@link UseProvider}.
+ */
+public final class ProviderOutcome {
+
+ // a generic failure without a cause returned by #failure()
+ private static final ProviderOutcome GENERIC_FAILURE = new ProviderOutcome(false, null, null);
+
+ // whether this is a success or failure
+ private final boolean success;
+
+ // the result value in case of success (may be null)
+ private final Object result;
+
+ // the reason for failure in case of failure (may be null)
+ private final Throwable cause;
+
+ /**
+ * Creates an outcome instance
+ *
+ * @param success {@code true} to indicate success or {@code false} to indicate failure
+ * @param result optional result value in case of success, may be {@code null}
+ * @param cause optional cause in case of failure, may be {@code null}
+ */
+ private ProviderOutcome(boolean success, Object result, Throwable cause) {
+ this.success = success;
+ this.result = result;
+ this.cause = cause;
+ }
+
+ /**
+ * Create a successful outcome
+ *
+ * @param result the result
+ * @return a successful result
+ */
+ public static ProviderOutcome success(Object result) {
+ return new ProviderOutcome(true, result, null);
+ }
+
+ /**
+ * Create a failed outcome without a specific {@link #getCause() cause}. This method must be used for creating outcomes that don't
+ * signal an error but rather the fact that the {@link UseProvider} is not capable of fulfilling the request.
+ *
+ * @return a failed outcome
+ */
+ public static ProviderOutcome failure() {
+ return GENERIC_FAILURE;
+ }
+
+ /**
+ * Create a failed outcome with the given {@link #getCause() cause}. This method must be used when the {@link UseProvider} is
+ * capable of fulfilling the request but an error condition prevents the provider from doing so.
+ *
+ * @param cause The reason for this failure, which may be {@code null}
+ * @return a failed outcome
+ */
+ public static ProviderOutcome failure(Throwable cause) {
+ return new ProviderOutcome(false, null, cause);
+ }
+
+ /**
+ * If the given obj is not {@code null} return a {@link #success(Object) successful outcome}, with the given result. Otherwise, return
+ * {@link #failure()}.
+ *
+ * @param obj the result
+ * @return an outcome based on whether the parameter is null or not
+ */
+ public static ProviderOutcome notNullOrFailure(Object obj) {
+ return (obj == null) ? failure() : success(obj);
+ }
+
+ /**
+ * Check if the outcome has been successful
+ *
+ * @return the outcome success status
+ */
+ public boolean isSuccess() {
+ return success;
+ }
+
+ /**
+ * Check whether the outcome is a failure
+ *
+ * @return the outcome failure status
+ */
+ public boolean isFailure() {
+ return !isSuccess();
+ }
+
+ /**
+ * Get the result in this outcome.
+ *
+ * @return the result of the container
+ * @throws IllegalStateException if the outcome is a failure
+ */
+ public Object getResult() {
+ if (!success) {
+ throw new IllegalStateException("Outcome has not been successful");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the cause for this failure outcome or {@code null} if this outcome is a success or no cause has been defined with the
+ * {@link #failure(Throwable)} method.
+ *
+ * @return the cause for this failure outcome.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/use/UseProvider.java b/src/main/java/org/apache/sling/scripting/sightly/use/UseProvider.java
new file mode 100644
index 0000000..14a78ba
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/use/UseProvider.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.use;
+
+import javax.script.Bindings;
+
+import org.apache.sling.scripting.sightly.render.RenderContext;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * <p>
+ * A {@code UseProvider} instantiates objects for the Use-API.
+ * </p>
+ */
+@ConsumerType
+public interface UseProvider {
+
+ /**
+ * Provide an instance based on the given identifier
+ *
+ * @param identifier the identifier of the dependency
+ * @param renderContext the current rendering context
+ * @param arguments specific arguments provided by the use plugin
+ * @return a container with the instance that corresponds to the identifier; if the identifier cannot be
+ * handled by this provider, a failed outcome is returned.
+ */
+ ProviderOutcome provide(String identifier, RenderContext renderContext, Bindings arguments);
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/use/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/use/package-info.java
new file mode 100644
index 0000000..0dfe431
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/use/package-info.java
@@ -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.
+ ******************************************************************************/
+@Version("1.0.1")
+package org.apache.sling.scripting.sightly.use;
+
+import org.osgi.annotation.versioning.Version;
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModelTest.java b/src/test/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModelTest.java
new file mode 100644
index 0000000..ab9c07b
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModelTest.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * 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.render;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.sling.scripting.sightly.Record;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class AbstractRuntimeObjectModelTest {
+
+ private AbstractRuntimeObjectModel runtimeObjectModel = new AbstractRuntimeObjectModel() {};
+
+ @Test
+ public void testResolveProperty() {
+ assertNull(runtimeObjectModel.resolveProperty(null, null));
+ assertNull(runtimeObjectModel.resolveProperty(this, null));
+ assertNull(runtimeObjectModel.resolveProperty(this, ""));
+ assertEquals(0, runtimeObjectModel.resolveProperty(Collections.EMPTY_LIST, "size"));
+ assertNull(runtimeObjectModel.resolveProperty(null, null));
+ int[] ints = new int[] {1, 2, 3};
+ assertEquals(ints.length, runtimeObjectModel.resolveProperty(ints, "length"));
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ assertEquals(testArray.length, runtimeObjectModel.resolveProperty(testArray, "length"));
+ assertEquals(2, runtimeObjectModel.resolveProperty(testArray, 1));
+ assertNull(runtimeObjectModel.resolveProperty(testArray, 3));
+ assertNull(runtimeObjectModel.resolveProperty(testArray, -1));
+ List<Integer> testList = Arrays.asList(testArray);
+ assertEquals(2, runtimeObjectModel.resolveProperty(testList, 1));
+ assertNull(runtimeObjectModel.resolveProperty(testList, 3));
+ assertNull(runtimeObjectModel.resolveProperty(testList, -1));
+ Map<String, Integer> map = new HashMap<String, Integer>() {{
+ put("one", 1);
+ put("two", 2);
+ }};
+ assertEquals(1, runtimeObjectModel.resolveProperty(map, "one"));
+ assertNull(runtimeObjectModel.resolveProperty(map, null));
+ assertNull(runtimeObjectModel.resolveProperty(map, ""));
+ Map<Integer, String> stringMap = new HashMap<Integer, String>(){{
+ put(1, "one");
+ put(2, "two");
+ }};
+ assertEquals("one", runtimeObjectModel.resolveProperty(stringMap, 1));
+ assertEquals("two", runtimeObjectModel.resolveProperty(stringMap, 2));
+ Map<String, String> strings = new HashMap<String, String>(){{
+ put("a", "one");
+ put("b", "two");
+ }};
+ Record<String> record = new Record<String>() {
+ @Override
+ public String getProperty(String name) {
+ return strings.get(name);
+ }
+
+ @Override
+ public Set<String> getPropertyNames() {
+ return strings.keySet();
+ }
+ };
+ assertEquals("one", runtimeObjectModel.resolveProperty(record, "a"));
+ }
+
+ @Test
+ public void testToDate() {
+ assertNull(runtimeObjectModel.toDate(null));
+ Date testDate = new Date();
+ assertEquals(testDate, runtimeObjectModel.toDate(testDate));
+ Calendar testCalendar = Calendar.getInstance();
+ assertEquals(testCalendar.getTime(), runtimeObjectModel.toDate(testCalendar));
+ }
+
+ @Test
+ public void testGetPropertyNullChecks() {
+ assertNull(runtimeObjectModel.getProperty(null, null));
+ assertNull(runtimeObjectModel.getProperty(this, null));
+ assertNull(runtimeObjectModel.getProperty(this, ""));
+ }
+
+ @Test
+ public void testIsDate() {
+ assertFalse(runtimeObjectModel.isDate(null));
+ assertTrue(runtimeObjectModel.isDate(new Date()));
+ assertTrue(runtimeObjectModel.isDate(Calendar.getInstance()));
+ }
+
+ @Test
+ public void testIsNumber() {
+ assertFalse(runtimeObjectModel.isNumber(null));
+ assertFalse(runtimeObjectModel.isNumber(""));
+ assertTrue(runtimeObjectModel.isNumber(0));
+ assertTrue(runtimeObjectModel.isNumber(0.5));
+ assertTrue(runtimeObjectModel.isNumber("0"));
+ assertTrue(runtimeObjectModel.isNumber("0.5"));
+ }
+
+ @Test
+ public void testToCollection() {
+ assertTrue(runtimeObjectModel.toCollection(null).isEmpty());
+ Record<String> record = new Record<String>() {
+
+ private Map<String, String> properties = new HashMap<String, String>() {{
+ put("a", "1");
+ put("b", "2");
+ }};
+
+ @Override
+ public String getProperty(String name) {
+ return properties.get(name);
+ }
+
+ @Override
+ public Set<String> getPropertyNames() {
+ return properties.keySet();
+ }
+ };
+ Collection testCollection = runtimeObjectModel.toCollection(record);
+ assertEquals(2, testCollection.size());
+ assertTrue(testCollection.contains("a"));
+ assertTrue(testCollection.contains("b"));
+ }
+
+ @Test
+ public void testToMap() {
+ final Map<String, String> properties = new HashMap<String, String>() {{
+ put("a", "1");
+ put("b", "2");
+ }};
+ assertEquals(properties, runtimeObjectModel.toMap(properties));
+ Record<String> record = new Record<String>() {
+ @Override
+ public String getProperty(String name) {
+ return properties.get(name);
+ }
+
+ @Override
+ public Set<String> getPropertyNames() {
+ return properties.keySet();
+ }
+ };
+ assertEquals(properties, runtimeObjectModel.toMap(record));
+ assertTrue(runtimeObjectModel.toMap(null).isEmpty());
+ }
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/ObjectModelTest.java b/src/test/java/org/apache/sling/scripting/sightly/render/ObjectModelTest.java
new file mode 100644
index 0000000..28f88bd
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/ObjectModelTest.java
@@ -0,0 +1,245 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+import org.apache.sling.scripting.sightly.render.testobjects.Person;
+import org.apache.sling.scripting.sightly.render.testobjects.TestEnum;
+import org.apache.sling.scripting.sightly.render.testobjects.internal.AdultFactory;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class ObjectModelTest {
+
+ @Test
+ public void testToBoolean() {
+ assertFalse(ObjectModel.toBoolean(null));
+ assertFalse(ObjectModel.toBoolean(0));
+ assertTrue(ObjectModel.toBoolean(123456));
+ assertFalse(ObjectModel.toBoolean(""));
+ assertFalse(ObjectModel.toBoolean("FalSe"));
+ assertFalse(ObjectModel.toBoolean("false"));
+ assertFalse(ObjectModel.toBoolean("FALSE"));
+ assertTrue(ObjectModel.toBoolean("true"));
+ assertTrue(ObjectModel.toBoolean("TRUE"));
+ assertTrue(ObjectModel.toBoolean("TrUE"));
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ int[] testPrimitiveArray = new int[] {1, 2, 3};
+ List testList = Arrays.asList(testArray);
+ assertTrue(ObjectModel.toBoolean(testArray));
+ assertTrue(ObjectModel.toBoolean(testPrimitiveArray));
+ assertFalse(ObjectModel.toBoolean(new Integer[]{}));
+ assertTrue(ObjectModel.toBoolean(testList));
+ assertFalse(ObjectModel.toBoolean(Collections.emptyList()));
+ Map<String, Integer> map = new HashMap<String, Integer>() {{
+ put("one", 1);
+ put("two", 2);
+ }};
+ assertTrue(ObjectModel.toBoolean(map));
+ assertFalse(ObjectModel.toBoolean(Collections.EMPTY_MAP));
+ assertTrue(ObjectModel.toBoolean(testList.iterator()));
+ assertFalse(ObjectModel.toBoolean(Collections.EMPTY_LIST.iterator()));
+ assertTrue(ObjectModel.toBoolean(new Bag<>(testArray)));
+ assertFalse(ObjectModel.toBoolean(new Bag<>(new Integer[]{})));
+ assertTrue(ObjectModel.toBoolean(new Date()));
+ }
+
+ @Test
+ public void testToNumber() {
+ assertEquals(1, ObjectModel.toNumber(1));
+ assertEquals(1, ObjectModel.toNumber("1"));
+ assertNull(ObjectModel.toNumber(null));
+ assertNull(ObjectModel.toNumber("1-2"));
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("", ObjectModel.toString(null));
+ assertEquals("1", ObjectModel.toString("1"));
+ assertEquals("1", ObjectModel.toString(1));
+ assertEquals("CONSTANT", ObjectModel.toString(TestEnum.CONSTANT));
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ int[] testPrimitiveArray = new int[] {1, 2, 3};
+ List testList = Arrays.asList(testArray);
+ assertEquals("1,2,3", ObjectModel.toString(testList));
+ assertEquals("1,2,3", ObjectModel.toString(testArray));
+ assertEquals("1,2,3", ObjectModel.toString(testPrimitiveArray));
+ }
+
+ @Test
+ public void testToCollection() {
+ assertTrue(ObjectModel.toCollection(null).isEmpty());
+ assertTrue(ObjectModel.toCollection(new StringBuilder()).isEmpty());
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ int[] testPrimitiveArray = new int[]{1, 2, 3};
+ List<Integer> testList = Arrays.asList(testArray);
+ Map<String, Integer> map = new HashMap<String, Integer>() {{
+ put("one", 1);
+ put("two", 2);
+ }};
+ assertEquals(testList, ObjectModel.toCollection(testArray));
+ assertEquals(testList, ObjectModel.toCollection(testPrimitiveArray));
+ assertEquals(testList, ObjectModel.toCollection(testList));
+ assertEquals(map.keySet(), ObjectModel.toCollection(map));
+ Vector<Integer> vector = new Vector<>(testList);
+ assertEquals(testList, ObjectModel.toCollection(vector.elements()));
+ assertEquals(testList, ObjectModel.toCollection(testList.iterator()));
+ assertEquals(testList, ObjectModel.toCollection(new Bag<>(testArray)));
+ String stringObject = "test";
+ Integer numberObject = 1;
+ Collection stringCollection = ObjectModel.toCollection(stringObject);
+ assertTrue(stringCollection.size() == 1 && stringCollection.contains(stringObject));
+ Collection numberCollection = ObjectModel.toCollection(numberObject);
+ assertTrue(numberCollection.size() == 1 && numberCollection.contains(numberObject));
+ }
+
+ @Test
+ public void testCollectionToString() {
+ assertEquals("", ObjectModel.collectionToString(null));
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ List testList = Arrays.asList(testArray);
+ assertEquals("1,2,3", ObjectModel.collectionToString(testList));
+ }
+
+ @Test
+ public void testFromIterator() {
+ assertTrue(ObjectModel.fromIterator(null).isEmpty());
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ List testList = Arrays.asList(testArray);
+ assertEquals(testList, ObjectModel.fromIterator(testList.iterator()));
+ }
+
+ @Test
+ public void testResolveProperty() {
+ assertNull(ObjectModel.resolveProperty(null, 0));
+ assertNull(ObjectModel.resolveProperty(this, null));
+ assertNull(ObjectModel.resolveProperty(null, null));
+ assertEquals(0, ObjectModel.resolveProperty(Collections.EMPTY_LIST, "size"));
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ assertEquals(2, ObjectModel.resolveProperty(testArray, 1));
+ assertNull(ObjectModel.resolveProperty(testArray, 3));
+ assertNull(ObjectModel.resolveProperty(testArray, -1));
+ List<Integer> testList = Arrays.asList(testArray);
+ assertEquals(2, ObjectModel.resolveProperty(testList, 1));
+ assertNull(ObjectModel.resolveProperty(testList, 3));
+ assertNull(ObjectModel.resolveProperty(testList, -1));
+ Map<String, Integer> map = new HashMap<String, Integer>() {{
+ put("one", 1);
+ put("two", 2);
+ }};
+ assertEquals(1, ObjectModel.resolveProperty(map, "one"));
+ assertNull(ObjectModel.resolveProperty(map, null));
+ assertNull(ObjectModel.resolveProperty(map, ""));
+ Map<Integer, String> stringMap = new HashMap<Integer, String>(){{
+ put(1, "one");
+ put(2, "two");
+ }};
+ assertEquals("one", ObjectModel.resolveProperty(stringMap, 1));
+ assertEquals("two", ObjectModel.resolveProperty(stringMap, 2));
+ Person johnDoe = AdultFactory.createAdult("John", "Doe");
+ assertEquals("Expected to be able to access public static final constants.", 1l, ObjectModel.resolveProperty(johnDoe, "CONSTANT"));
+ assertNull("Did not expect to be able to access public fields from package protected classes.", ObjectModel.resolveProperty(johnDoe,
+ "TODAY"));
+ assertEquals("Expected to be able to access an array's length property.", 3, ObjectModel.resolveProperty(testArray, "length"));
+ assertNotNull("Expected not null result for invocation of interface method on implementation class.",
+ ObjectModel.resolveProperty(johnDoe, "lastName"));
+ assertNull("Expected null result for public method available on implementation but not exposed by interface.", ObjectModel
+ .resolveProperty(johnDoe, "fullName"));
+ assertNull("Expected null result for inexistent method.", ObjectModel.resolveProperty(johnDoe, "nomethod"));
+ }
+
+ @Test
+ public void testGetIndex() {
+ assertNull(ObjectModel.getIndex(null, 0));
+ Integer[] testArray = new Integer[] {1, 2, 3};
+ assertEquals(2, ObjectModel.getIndex(testArray, 1));
+ assertNull(ObjectModel.getIndex(testArray, 3));
+ assertNull(ObjectModel.getIndex(testArray, -1));
+ List<Integer> testList = Arrays.asList(testArray);
+ assertEquals(2, ObjectModel.getIndex(testList, 1));
+ assertNull(ObjectModel.getIndex(testList, 3));
+ assertNull(ObjectModel.getIndex(testList, -1));
+ Map<Integer, String> stringMap = new HashMap<Integer, String>(){{
+ put(1, "one");
+ put(2, "two");
+ }};
+ assertNull(ObjectModel.getIndex(stringMap, 1));
+ assertNull(ObjectModel.getIndex(stringMap, 2));
+ }
+
+ @Test
+ public void testClassBasedMethodsForNulls() {
+ assertNull(ObjectModel.getField(null, null));
+ assertNull(ObjectModel.getField("", null));
+ assertNull(ObjectModel.getField(this, ""));
+ assertNull(ObjectModel.findBeanMethod(null, null));
+ assertNull(ObjectModel.findBeanMethod(this.getClass(), null));
+ assertNull(ObjectModel.findBeanMethod(this.getClass(), ""));
+ assertNull(ObjectModel.invokeBeanMethod(null, null));
+ assertNull(ObjectModel.invokeBeanMethod(this, null));
+ assertNull(ObjectModel.invokeBeanMethod(this, ""));
+ }
+
+
+ private class Bag<T> implements Iterable<T> {
+
+ private T[] backingArray;
+
+ public Bag(T[] array) {
+ this.backingArray = array;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return new Iterator<T>() {
+
+ int index = 0;
+
+ @Override
+ public boolean hasNext() {
+ return index < backingArray.length;
+ }
+
+ @Override
+ public T next() {
+ return backingArray[index++];
+ }
+
+ @Override
+ public void remove() {
+
+ }
+ };
+ }
+ }
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/Person.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/Person.java
new file mode 100644
index 0000000..fc0c3f8
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/Person.java
@@ -0,0 +1,29 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render.testobjects;
+
+public interface Person {
+
+ long CONSTANT = 1;
+
+ String getFirstName();
+
+ String getLastName();
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/TestEnum.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/TestEnum.java
new file mode 100644
index 0000000..49c4072
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/TestEnum.java
@@ -0,0 +1,25 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render.testobjects;
+
+public enum TestEnum {
+
+ CONSTANT
+
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AbstractPerson.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AbstractPerson.java
new file mode 100644
index 0000000..121cd8e
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AbstractPerson.java
@@ -0,0 +1,42 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render.testobjects.internal;
+
+import org.apache.sling.scripting.sightly.render.testobjects.Person;
+
+abstract class AbstractPerson implements Person {
+
+ private String firstName;
+ private String lastName;
+
+ public AbstractPerson(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+
+ @Override
+ public String getFirstName() {
+ return firstName;
+ }
+
+ @Override
+ public String getLastName() {
+ return lastName;
+ }
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/Adult.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/Adult.java
new file mode 100644
index 0000000..10df9b0
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/Adult.java
@@ -0,0 +1,32 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render.testobjects.internal;
+
+class Adult extends AbstractPerson {
+
+ public static final long TODAY = System.currentTimeMillis();
+
+ Adult(String firstName, String lastName) {
+ super(firstName, lastName);
+ }
+
+ public String getFullName() {
+ return getFirstName() + ", " + getLastName();
+ }
+}
diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AdultFactory.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AdultFactory.java
new file mode 100644
index 0000000..b2f879b
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AdultFactory.java
@@ -0,0 +1,26 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.render.testobjects.internal;
+
+public class AdultFactory {
+
+ public static Adult createAdult(String firstName, String lastName) {
+ return new Adult(firstName, lastName);
+ }
+}