Hello,
over last week-end, i've implemented an XML template processor using the Java 
22 state of the spec (using old template processor syntax) and i would like to 
propose to see the processing of a string template as a two steps process.

I will use the XML template processor i've developed as an example,
  https://github.com/forax/html-component/blob/master/src/test/java/Demo.java

Here is how it works, the idea is that if i want to generate the XML of a 
product, i will write something like this.

record Product(String name, int price) implements Component {
  public Renderer render() {
    return $."""
          <tr class=".product">
            <td>\{name}</td><td>\{price * 1.20}</td>
          </tr>
          """;
  }
}

Component is an interface with only one method render() that returns a Renderer 
and a Renderer is also an interface that is able to send XML events.
And "$" is the name of the template processor defined in Component as a static 
field.

The code of the template processor is here
  
https://github.com/forax/html-component/blob/master/src/main/java/com/github/forax/htmlcomponent/ComponentTemplateProcessor.java#L193

Conceptually, what a template processor should do is a two step process, first 
validate the template, in my case validate that the template is a valid XML 
fragment and then interpolate the result of the validation using the arguments 
of the template.

So processing a sting template is currently
  process(StringTemplate) <=> { validate(StringTemplate); 
interpolate(StringTemplate); }

There are two main shortcomings of the idea that processing a string template 
is equivalent to calling a method that takes a StringTemplate.
- (notypes) the types of the holes are no propagated to the StringTemplate, so 
the validation part can not verify that the template is correctly typed.
- (cache) the validation part has to be re-executed each time.

To illustrate the issue (notype), I can have a XML fragment that depends on 
another class, but i've no way to test if the referenced Product is a record 
that takes a name of type String and a price of type int because while those 
types are known by the compiler, they are not available into the String 
Template.

record Cart() implements Component {
  public Renderer render() {
    return $."""
          <table>
            <Product name="wood" price="\{10}"/>
            <Product name="cristal" price="\{300}"/>
          </table>
          """;
  }
}  

To illustrate the issue (cache), in the code above, i've two calls to rend a 
Product with different attributes, but for each call to Product::render(), the 
validation step will be re-executed. As an implementer, I can try to cache the 
result of the validation but that's far from easy, very bug prone and 
ultimately not very efficient.

Given that a string template literal is a literal, i propose that the Java 
runtime helps by doing the caching of the validation step.

The simplest way I see for that is to separate string template in two, a 
constant template part composed of the fragments (List<String>) and the types 
(List<Class<?>>) from the non constant part, the arguments of the template 
(List<Object>).

For that, we need a user-defined intermediary object that correspond to the 
result of the validation, the creation of this object is the proof that the 
string template is validated and this object can be cached by the JDK runtime.

In that case, processing a string template is equivalent to
  var cached userDefinedValidatedTemplate = validateAndCreate(List<String> 
fragment, List<Class<?>> types);
  process(userDefinedValidatedTemplate, arguments); }


So
- I propose that StringTemplate is the tuple List<String> fragment, 
List<Class<?>> types.

- Users can create a special template validated class, with a factory method 
that takes a StringTemplate and is tagged a being a template validator
   
  for example
    __template_validated__ class ValidatedXMLDOM {
        ...
        public static __template__validator__ ValidatedXMLDOM of(StringTemplate 
stringTemplate) { ... }
    }
   
- a processor method is a method that takes a __template_validated__ object 
followed by parameters storing the template string arguments
  By example
    processXML(ValidatedXMLDOM dom, Object... arguments)

At compile time, either processXML is called using an invokedynamic or the 
__template_validated__ instance is computed with a constant dynamic or both.
But the idea is that the generated bytecode ensure that the 
__template_validated__ instance is created once and cached.

This is a rough sketch, a lot of details are up to debate but i think we should 
start to think that the template processing is a two steps process.

Rémi

Reply via email to